Skip to content

Conversation

@qdaxb
Copy link
Contributor

@qdaxb qdaxb commented Nov 27, 2025

Summary

  • Add Models tab to settings page (between Team and Integrations)
  • Create ModelList component for viewing/managing model configurations
  • Create ModelEdit component for creating/editing models with:
    • Provider type selection (OpenAI/Anthropic)
    • Model ID presets and custom option
    • API Key input with visibility toggle
    • Base URL configuration
    • Connection test functionality
  • Add backend endpoints:
    • POST /models/test-connection for validating API credentials
    • GET /models/compatible for filtering models by agent type
  • Update TaskCreate schema with model_id and force_override_bot_model fields
  • Enhance executor_kinds.py model resolution logic:
    • Support bind_model in agent_config
    • Support task-level model override with force_override flag
    • Query kinds table first, fallback to public_models (legacy)
    • Maintain backward compatibility with private_model
  • Add i18n translations for models (zh-CN and en)

Test plan

  • Verify Models tab appears in Settings page (Desktop and Mobile layouts)
  • Test creating a new OpenAI model with valid credentials
  • Test creating a new Anthropic model with valid credentials
  • Test connection validation with both valid and invalid API keys
  • Test editing an existing model
  • Test deleting a model
  • Verify model list correctly displays provider type and model ID tags
  • Verify i18n translations work for both Chinese and English
  • Test backward compatibility: existing Bots with private_model should continue to work

Summary by CodeRabbit

  • New Features

    • Full Models management: unified model view (public + user), create/edit modal, test-connection, delete, and compatibility listing.
    • In-chat Model selector with per-task model selection and force-override toggle.
  • UX

    • Settings relabeled to "Models"; Models list/edit surfaced in Settings.
    • Drag-and-drop ordering for transfer lists; new solo/pipeline/leader mode editors and related dialogs.
  • Documentation

    • Expanded English and Chinese localization for Models and related flows.
  • Chores

    • Encryption utilities extended to support API key handling and masking.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add Models tab to settings page (between Team and Integrations)
- Create ModelList component for viewing/managing model configurations
- Create ModelEdit component for creating/editing models with:
  - Provider type selection (OpenAI/Anthropic)
  - Model ID presets and custom option
  - API Key input with visibility toggle
  - Base URL configuration
  - Connection test functionality
- Add backend endpoints:
  - POST /models/test-connection for validating API credentials
  - GET /models/compatible for filtering models by agent type
- Update TaskCreate schema with model_id and force_override_bot_model fields
- Enhance executor_kinds.py model resolution logic:
  - Support bind_model in agent_config
  - Support task-level model override with force_override flag
  - Query kinds table first, fallback to public_models (legacy)
  - Maintain backward compatibility with private_model
- Add i18n translations for models (zh-CN and en)

This enables users to manage AI model configurations independently
and bind them to Bots, supporting both OpenAI and Anthropic providers.
@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds unified model discovery/service and new adapter endpoints; wires CRD-based model resolution into executors, bots, teams, and tasks; adds API-key encryption helpers; extends frontend with model CRUD, selector, and settings UI; introduces i18n strings and UI primitives; plus tests and small infra tweaks.

Changes

Cohort / File(s) Summary
Backend — unified model API & service
backend/app/api/endpoints/adapter/models.py, backend/app/services/model_aggregation_service.py
New endpoints to list/resolve unified models, test provider connections, and enumerate compatible CRD models; adds ModelAggregationService, UnifiedModel, ModelType enum, parsing, compatibility and resolution helpers.
Backend — executor / bot / team model wiring
backend/app/services/adapters/executor_kinds.py, backend/app/services/adapters/bot_kinds.py, backend/app/services/adapters/team_kinds.py, backend/app/services/adapters/task_kinds.py
Multi-step model resolution (task override → bot bind → Kind CRD/user model → PublicModel fallback), modelRef handling, protocol extraction, bot summaries, task metadata labels (modelId, forceOverrideBotModel), decryption of API keys, and added logging/fallbacks.
Backend — Kind schema & Kind service impl
backend/app/schemas/kind.py, backend/app/services/kind_impl.py
Kind schema extended (displayName, isCustomConfig, protocol; BotSpec.modelRef optional); ModelKindService encrypts API keys on persist and decrypts when formatting responses; added extraction/format helpers.
Backend — PublicModel listing / adapters
backend/app/services/adapters/public_model.py
PublicModel listing reworked to aggregate/deduplicate user Kind CRDs and public models with provider/compatibility filtering and displayName support.
Backend — endpoints & logging / misc
backend/app/api/endpoints/adapter/bots.py, backend/app/api/endpoints/kind/kinds.py, backend/init_data/01-default-resources.yaml
Added debug logging in bot endpoints; delete kind endpoint now returns JSON message (was 204); removed modelRef linkages in init data in favor of shellRef.
Backend — crypto utilities & tests
shared/utils/crypto.py, shared/tests/utils/test_crypto.py
Generalized encryption utilities (sensitive data and API-key helpers), env-based key loading, wrappers for backward compatibility, masking helpers; tests expanded.
Frontend — model API types & client
frontend/src/apis/models.ts, frontend/src/apis/tasks.ts
New CRD and unified model types, ModelTypeEnum, test/compatible types; modelApis extended with CRUD/test/compat endpoints; CreateTaskRequest extended with model_id and force_override_bot_model.
Frontend — settings UI: Models tab & components
frontend/src/app/settings/page.tsx, frontend/src/features/settings/components/ModelList.tsx, frontend/src/features/settings/components/ModelEdit.tsx
Settings page replaces Bots with Models tab; added ModelList and ModelEdit components for CRUD and test flows; routing/tab mappings updated.
Frontend — task UI: ModelSelector & integration
frontend/src/features/tasks/components/ModelSelector.tsx, frontend/src/features/tasks/components/ChatArea.tsx, frontend/src/features/tasks/service/messageService.ts
New ModelSelector component, ChatArea integration, and sendMessage now passes model_id and force_override_bot_model.
Frontend — team/mode editors & transfer UX
frontend/src/features/settings/components/* (BotEdit.tsx, TeamEdit.tsx, team-modes/*, BotTransfer, Solo/Pipeline/Leader editors)
New/updated team-mode editors, BotTransfer with drag-ordering, BotEdit enhancements for predefined/custom models and protocol, allowedAgents wiring, and barrel exports/types.
Frontend — UI primitives & dependency
frontend/src/components/ui/alert-dialog.tsx, frontend/package.json
Added Radix-based AlertDialog wrapper component and dependency @radix-ui/react-alert-dialog.
Frontend — i18n
frontend/src/i18n/locales/en/common.json, frontend/src/i18n/locales/zh-CN/common.json
Added comprehensive models translation blocks, settings.models label, and many new keys for model management, validation, and task UI.
Frontend — types & client helpers
frontend/src/types/api.ts, frontend/src/features/settings/services/bots.ts
Added BotSummary and is_mix_team to API types; client helpers for detecting/creating predefined model configs and model-type helpers.
Frontend — transfer ordering, UI tweaks & styles
frontend/src/components/ui/transfer.tsx, frontend/src/features/settings/components/TeamList.tsx, frontend/tailwind.config.js, frontend/src/features/settings/components/TeamEdit.module.css
Added drag-and-drop ordering for transfer right list, BotList dialog wiring, Tailwind color alias additions, and responsive TeamEdit styles.
Other frontend additions
many new UI components (team-modes/*, ModelSelector, ModelList/Edit, AlertDialog)
New components, props and types across settings and task UIs; accessibility and state persistence for model selection.
Misc infra & executors
executor_manager/executors/docker/utils.py, executor_manager/main.py
Default callback/server ports changed from 8080 → 8001.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Frontend (ModelList/ModelEdit/ModelSelector)
    participant API as Backend API (adapter models endpoints)
    participant Aggreg as ModelAggregationService
    participant DB as DB (Kind CRDs & PublicModel)
    participant Provider as External Provider

    User->>UI: open models UI / select model / trigger test
    UI->>API: GET /adapter/models or POST /adapter/models/test-connection
    API->>Aggreg: list_available_models / resolve_model (db, user, agent)
    Aggreg->>DB: query Kind CRDs and PublicModel
    DB-->>Aggreg: return CRD / public entries
    Aggreg-->>API: aggregated model list or resolved model (with config)
    alt test connection
        API->>Provider: probe (OpenAI/Anthropic) using decrypted key
        Provider-->>API: success / error
        API-->>UI: {success, message}
    else CRUD
        UI->>API: POST/PUT/DELETE model
        API->>DB: persist Kind CRD or update PublicModel
        DB-->>API: ack
        API-->>UI: result
    end
Loading
sequenceDiagram
    participant Task as Task Runner
    participant Executor as ExecutorKinds
    participant Aggreg as ModelAggregationService
    participant DB as DB (Kind CRDs & PublicModel)

    Task->>Executor: start(task includes optional modelId/forceOverride)
    Executor->>Executor: determine model_name_to_use (task override -> bot bind -> legacy)
    Executor->>Aggreg: resolve_model(name, maybe type)
    Aggreg->>DB: fetch Kind or PublicModel
    DB-->>Aggreg: CRD or public entry
    Aggreg-->>Executor: model config (api_key decrypted if present)
    Executor->>Executor: apply protocol/base_url and continue execution
    alt fallback path
        Executor->>Executor: use original agent_config / legacy PublicModel lookup
    end
    Executor-->>Task: execution proceeds with resolved config
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • executor_kinds.py: multi-step resolution, decryption usage, and fallback correctness.
    • bot_kinds.py & team_kinds.py: modelRef lifecycle (create/update/delete), bot summary enrichment, and new response shapes.
    • model_aggregation_service.py: CRD parsing, provider/agent compatibility, include_config behavior.
    • shared/utils/crypto.py & tests: env-based key loading, compatibility wrappers, and masking.
    • Frontend ModelEdit/ModelList/ModelSelector: validation, testConnection flow, persistence/restore, and alignment with backend types.

Possibly related PRs

Suggested reviewers

  • feifei325
  • Micro66

Poem

🐰 I nibble keys and hop on logs,
CRDs and models snug in clogs,
I test a link and pick a name,
Tabs and selectors join the game,
Hooray — models hop and wag their flags!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat(settings): add model management module' accurately describes the main objective and primary change—introducing a new Model Management module to the Settings interface.
Docstring Coverage ✅ Passed Docstring coverage is 81.55% which is sufficient. The required threshold is 80.00%.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2664be3 and eff23d4.

📒 Files selected for processing (4)
  • backend/app/services/adapters/executor_kinds.py (4 hunks)
  • backend/app/services/adapters/task_kinds.py (2 hunks)
  • executor_manager/executors/docker/utils.py (1 hunks)
  • executor_manager/main.py (2 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
backend/app/services/adapters/executor_kinds.py (1)

440-443: Use logger.exception to preserve stack trace.

When catching exceptions in the fallback handler, logger.exception will automatically include the traceback, which aids debugging production issues.

                 except Exception as e:
-                    logger.error(f"Failed to resolve model config: {e}")
+                    logger.exception(f"Failed to resolve model config: {e}")
                     # On any error, fallback to original agent_config
                     agent_config_data = agent_config
frontend/src/features/settings/components/ModelList.tsx (1)

27-257: ModelList logic is solid; consider aligning chip styling with design tokens

The CRUD flow, connection testing, and list/empty/loading states are wired correctly against the CRD APIs and i18n keys; the per-row action handling looks sound. For the provider badges (getProviderStyle), you might later swap the raw Tailwind greens/oranges for your existing theme variables (e.g., bg-muted + text tokens) to keep colors consistent with the rest of the app.

backend/app/api/endpoints/adapter/models.py (1)

255-306: Make compatible-model filtering more robust and align with SQLAlchemy style

The overall idea—filtering Model CRDs by env.model for a given agent—is good, but a few details could be tightened:

  • ModelConfig shape: model_crd.spec.modelConfig is treated as a dict, but if the Pydantic ModelCRD schema uses a nested model for modelConfig, isinstance(..., dict) will be false and those models will be silently skipped. Consider handling both cases (e.g., env = model_crd.spec.modelConfig.env when it’s a model, else dict .get("env")), so compatible models aren’t dropped by accident.
  • Agent name matching: Right now agent_name must be exactly "Agno" or "ClaudeCode". It may be safer to normalize agent_name (e.g., .lower()) or centralize these identifiers to avoid subtle mismatches with frontend strings.
  • SQLAlchemy boolean comparison: Kind.is_active == True can be simplified to Kind.is_active.is_(True) or just Kind.is_active per SQLAlchemy best practices, which will also quiet static analysis.

These are mostly robustness and style tweaks; the core behavior is fine.

frontend/src/features/settings/components/ModelEdit.tsx (1)

29-217: ModelEdit flow is correct; refine validation messages and small nits

The create/edit and test-connection flows, CRD construction, and i18n usage all line up with the new backend APIs and types. A few minor points you might adjust:

  • In handleTestConnection, when either finalModelId or apiKey is missing you always show models.errors.model_id_required. Splitting the check so missing API key uses models.errors.api_key_required would give clearer feedback.
  • isPresetModelId is computed but never used; it can be removed or leveraged (e.g., to conditionally show the custom ID input).
  • When switching provider types on an existing model, handleProviderChange overwrites any existing baseUrl with the provider default. If users might have custom proxies per model, you may want to only set a default when creating (or when baseUrl is empty) rather than on every provider change.

None of these block merging, but they’ll smooth the UX and keep the component tidy.

Also applies to: 233-395

frontend/src/apis/models.ts (1)

7-33: CRD types and model APIs align well with the backend design

The ModelCRD/ModelListResponse/test-connection/compatible-model types and corresponding modelApis methods match the CRD-centric backend endpoints and what ModelList/ModelEdit need. This fits the Kubernetes-style CRD pattern noted for the project. As a tiny future polish, you might centralize the repeated '/kinds/namespaces/default/models' path/namespace into a constant to avoid drift if namespaces ever change.

Also applies to: 44-65, 68-123

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6621a25 and 6599305.

📒 Files selected for processing (9)
  • backend/app/api/endpoints/adapter/models.py (3 hunks)
  • backend/app/schemas/task.py (1 hunks)
  • backend/app/services/adapters/executor_kinds.py (1 hunks)
  • frontend/src/apis/models.ts (2 hunks)
  • frontend/src/app/settings/page.tsx (7 hunks)
  • frontend/src/features/settings/components/ModelEdit.tsx (1 hunks)
  • frontend/src/features/settings/components/ModelList.tsx (1 hunks)
  • frontend/src/i18n/locales/en/common.json (2 hunks)
  • frontend/src/i18n/locales/zh-CN/common.json (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**

📄 CodeRabbit inference engine (AGENTS.md)

Frontend: Only use NEXT_PUBLIC_* for client-safe environment variables

Files:

  • frontend/src/i18n/locales/en/common.json
  • frontend/src/features/settings/components/ModelList.tsx
  • frontend/src/i18n/locales/zh-CN/common.json
  • frontend/src/features/settings/components/ModelEdit.tsx
  • frontend/src/apis/models.ts
  • frontend/src/app/settings/page.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: TypeScript strict mode must be enabled in Frontend
Use functional components with hooks in React
Use Prettier formatter in Frontend with single quotes and no semicolons
Use ESLint with Next.js configuration in Frontend
Use const over let, never use var in TypeScript
Component names must use PascalCase; file names must use kebab-case in Frontend
Use mobile-first responsive design approach in Frontend components
Frontend design: Low saturation + low contrast for reduced eye strain with generous whitespace
Frontend design: Minimal shadows, subtle component differentiation (<10% background variance)
Frontend design: Use mint blue (#14B8A6) as primary accent sparingly
Use Tailwind CSS variables for colors: --color-bg-base, --color-text-primary, --color-border, etc.
Frontend spacing: Use standard units - p-2 (8px), p-4 (16px), p-6 (24px), gap-3 (12px)
Frontend border-radius: rounded-2xl (16px) for large containers, rounded-lg (12px) for cards, rounded-md (6px) for buttons
Frontend typography: H1 text-xl font-semibold, H2 text-lg font-semibold, H3 text-base font-medium, Body text-sm
Use shadcn/ui Button component with variants: default, secondary, ghost, outline, link
Use shadcn/ui Form component with react-hook-form + zod validation

Files:

  • frontend/src/features/settings/components/ModelList.tsx
  • frontend/src/features/settings/components/ModelEdit.tsx
  • frontend/src/apis/models.ts
  • frontend/src/app/settings/page.tsx
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must be PEP 8 compliant with Black formatter (line length: 88)
Use isort for import organization in Python
Python functions and variables must have descriptive names
Docstrings required for all public Python functions and classes
Extract magic numbers to named constants in Python
Python functions must not exceed 50 lines
Type hints required for all Python function signatures
Run pylint and flake8 for Python code quality checks

Files:

  • backend/app/api/endpoints/adapter/models.py
  • backend/app/services/adapters/executor_kinds.py
  • backend/app/schemas/task.py
🧠 Learnings (1)
📚 Learning: 2025-11-26T07:34:32.573Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.573Z
Learning: Use Kubernetes-inspired CRD design (Ghost, Model, Shell, Bot, Team, Task)

Applied to files:

  • frontend/src/apis/models.ts
🧬 Code graph analysis (6)
frontend/src/features/settings/components/ModelList.tsx (4)
frontend/src/hooks/use-toast.ts (2)
  • useToast (189-189)
  • toast (189-189)
frontend/src/apis/models.ts (2)
  • ModelCRD (8-28)
  • modelApis (67-123)
frontend/src/components/ui/button.tsx (1)
  • Button (55-55)
frontend/src/components/ui/card.tsx (1)
  • Card (75-75)
frontend/src/features/settings/components/ModelEdit.tsx (1)
frontend/src/apis/models.ts (2)
  • ModelCRD (8-28)
  • modelApis (67-123)
backend/app/api/endpoints/adapter/models.py (3)
backend/app/models/kind.py (1)
  • Kind (14-30)
backend/app/core/security.py (1)
  • get_current_user (27-50)
frontend/src/apis/models.ts (2)
  • Model (36-38)
  • ModelCRD (8-28)
frontend/src/apis/models.ts (1)
frontend/src/apis/client.ts (1)
  • apiClient (105-105)
backend/app/services/adapters/executor_kinds.py (2)
backend/app/models/kind.py (1)
  • Kind (14-30)
backend/app/schemas/kind.py (1)
  • Model (68-74)
frontend/src/app/settings/page.tsx (1)
backend/app/schemas/kind.py (1)
  • ModelList (77-81)
🪛 Ruff (0.14.6)
backend/app/api/endpoints/adapter/models.py

177-177: Unused function argument: current_user

(ARG001)


177-177: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


231-231: Local variable response is assigned to but never used

Remove assignment to unused variable response

(F841)


247-247: Do not catch blind exception: Exception

(BLE001)


248-248: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


248-248: Use explicit conversion flag

Replace with conversion flag

(RUF010)


251-251: Use explicit conversion flag

Replace with conversion flag

(RUF010)


258-258: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


259-259: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


282-282: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


302-302: Do not catch blind exception: Exception

(BLE001)

backend/app/services/adapters/executor_kinds.py

414-414: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


424-424: Do not catch blind exception: Exception

(BLE001)


440-440: Do not catch blind exception: Exception

(BLE001)


441-441: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🔇 Additional comments (7)
backend/app/schemas/task.py (1)

57-59: LGTM!

The new model selection fields are well-defined with appropriate type hints, sensible defaults, and helpful documentation clarifying that model_id represents a model name rather than a database ID.

backend/app/services/adapters/executor_kinds.py (3)

418-425: Inner exception handling looks appropriate.

The try-except with logger.warning is suitable here since CRD parsing failures gracefully fall back to the public_models table. The exception details are preserved in the warning message.


372-405: Model resolution priority logic is well-structured.

The resolution hierarchy (force override → bind_model → private_model → task fallback) is clearly documented and handles backward compatibility with the legacy private_model path.


427-438: PublicModel fallback path looks correct.

The import-inside-function pattern is acceptable for this fallback dependency. The query correctly omits user filtering since public models are shared across users.

frontend/src/i18n/locales/zh-CN/common.json (1)

270-270: Models i18n entries look consistent and complete

The new settings.models label and models.* block align well with the EN locale and the components’ usage (including all errors.* keys). I don’t see missing or inconsistent keys on the zh-CN side.

Also applies to: 431-469

frontend/src/i18n/locales/en/common.json (1)

269-269: English models i18n block matches UI and validation behavior

The settings.models label and models.* strings cover all flows (list, create/edit, delete, test, and errors) and are consistent with the zh-CN locale and the form validation logic.

Also applies to: 431-469

frontend/src/app/settings/page.tsx (1)

12-18: Models tab wiring (index, labels, and panels) is coherent

The tab index/name mappings, desktop and mobile Tab.List order, and Tab.Panels are all aligned with Models at index 2, so ?tab=models deep-linking and navigation state should behave correctly without impacting existing tabs.

Also applies to: 33-36, 45-48, 134-145, 182-184, 225-236, 274-276

Comment on lines 167 to 252
public_model_service.delete_model(db=db, model_id=model_id, current_user=current_user)
return {"message": "Model deleted successfully"}
return {"message": "Model deleted successfully"}


@router.post("/test-connection")
def test_model_connection(
test_data: dict,
current_user: User = Depends(security.get_current_user),
):
"""
Test model connection
Request body:
{
"provider_type": "openai" | "anthropic",
"model_id": "gpt-4",
"api_key": "sk-...",
"base_url": "https://api.openai.com/v1" // optional
}
Response:
{
"success": true | false,
"message": "Connection successful" | "Error message"
}
"""
provider_type = test_data.get("provider_type")
model_id = test_data.get("model_id")
api_key = test_data.get("api_key")
base_url = test_data.get("base_url")

if not provider_type or not model_id or not api_key:
return {
"success": False,
"message": "Missing required fields: provider_type, model_id, api_key"
}

try:
if provider_type == "openai":
import openai
client = openai.OpenAI(
api_key=api_key,
base_url=base_url or "https://api.openai.com/v1"
)
# Send minimal test request
response = client.chat.completions.create(
model=model_id,
messages=[{"role": "user", "content": "hi"}],
max_tokens=1
)
return {
"success": True,
"message": f"Successfully connected to {model_id}"
}

elif provider_type == "anthropic":
import anthropic
client = anthropic.Anthropic(api_key=api_key)
if base_url:
client.base_url = base_url

response = client.messages.create(
model=model_id,
max_tokens=1,
messages=[{"role": "user", "content": "hi"}]
)
return {
"success": True,
"message": f"Successfully connected to {model_id}"
}

else:
return {
"success": False,
"message": "Unsupported provider type"
}

except Exception as e:
logger.error(f"Model connection test failed: {str(e)}")
return {
"success": False,
"message": f"Connection failed: {str(e)}"
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check the file size and read the full file to understand context
wc -l backend/app/api/endpoints/adapter/models.py

Repository: wecode-ai/Wegent

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Read the file to see the full context around the flagged lines
head -n 260 backend/app/api/endpoints/adapter/models.py | tail -n 100

Repository: wecode-ai/Wegent

Length of output: 2984


🏁 Script executed:

#!/bin/bash
# Search for URL validation patterns in the codebase
rg -n "base_url|validate.*url|SSRF|localhost|127\.0\.0\.1" backend/ -t py | head -30

Repository: wecode-ai/Wegent

Length of output: 3058


🏁 Script executed:

#!/bin/bash
# Check for similar endpoint patterns and exception handling
rg -n "except Exception" backend/app/api/endpoints/ -t py -A 2 | head -40

Repository: wecode-ai/Wegent

Length of output: 2688


🏁 Script executed:

#!/bin/bash
# Check for type hints usage in this file
rg -n "def " backend/app/api/endpoints/adapter/models.py | head -20

Repository: wecode-ai/Wegent

Length of output: 277


🏁 Script executed:

#!/bin/bash
# Check imports and function signatures in the file
head -n 50 backend/app/api/endpoints/adapter/models.py

Repository: wecode-ai/Wegent

Length of output: 1520


🏁 Script executed:

#!/bin/bash
# Check if there's a Pydantic model for test connection request
rg -n "class.*Test.*Connection|class.*Model.*Test" backend/ -t py -A 3

Repository: wecode-ai/Wegent

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Verify the coding guidelines requirement for type hints
grep -r "Type hints required" backend/ || echo "Checking guidelines in documentation"
find . -name "*.md" -o -name "GUIDELINES*" | head -5

Repository: wecode-ai/Wegent

Length of output: 197


Harden /test-connection against SSRF and tighten error handling/logging

Functionality is correct for both OpenAI and Anthropic, but there are important concerns:

  • SSRF risk via base_url: Passing user-supplied base_url directly into the OpenAI/Anthropic clients lets authenticated users make the server call arbitrary hosts/ports. At minimum, validate base_url (scheme https only, no localhost/private IPs, or restrict to an allowlist / configured proxy domains) before using it, or consider dropping base_url from this endpoint and deriving it server-side from a controlled config.
  • Overly broad exception handling and leakage to clients: Catching Exception and returning f"Connection failed: {str(e)}" to the client exposes internal SDK error details. Prefer logging the full exception with logger.exception("Model connection test failed: %s", e) and returning a generic message (optionally plus a short, sanitized hint).
  • Unused variables: The response variables for both provider branches (lines 221, 236) are unused; remove assignments.
  • Missing type hints: Add explicit return type annotation (-> dict) and consider creating a Pydantic model for test_data instead of using dict for consistency with other endpoints in the file.
🧰 Tools
🪛 Ruff (0.14.6)

177-177: Unused function argument: current_user

(ARG001)


177-177: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


231-231: Local variable response is assigned to but never used

Remove assignment to unused variable response

(F841)


247-247: Do not catch blind exception: Exception

(BLE001)


248-248: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


248-248: Use explicit conversion flag

Replace with conversion flag

(RUF010)


251-251: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for AI Agents
In backend/app/api/endpoints/adapter/models.py around lines 170 to 252, harden
the test_model_connection endpoint by validating or removing user-supplied
base_url to prevent SSRF (accept only https, reject localhost/private
IPs/resolved addresses or prefer server-side configured/allowlisted proxy
domains), stop passing arbitrary base_url directly to SDK clients, replace broad
except usage with logger.exception("Model connection test failed") while
returning a generic sanitized error message to clients (do not include str(e)),
remove the unused response assignments in both provider branches, and add
explicit return type annotation (-> dict) and replace the plain dict request
body with a Pydantic request model (or at least add type hints) for consistent
validation.

Change API paths from /kinds/namespaces/... to /v1/namespaces/...
to match the backend k_router prefix configuration.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
frontend/src/apis/models.ts (2)

16-21: Align provider naming between env.model docs and provider_type union

Right now the inline comment on env.model suggests 'openai' | 'claude', while TestConnectionRequest.provider_type uses 'openai' | 'anthropic'. That mismatch is a bit confusing.

Consider:

  • Standardizing on one provider identifier set (likely 'openai' | 'anthropic') across both places, and
  • If the provider set is stable, narrowing env.model from string to the same union so config drift is caught at compile time.

If env.model is intentionally looser, at least updating the comment to reflect the real accepted values would avoid ambiguity.

Also applies to: 44-50


75-123: API surface is clear; consider minor reuse of CRD base path

The new CRUD methods and helper endpoints read clearly and use encodeURIComponent correctly on dynamic segments. One small maintainability tweak you might consider:

  • Extract a shared CRD base path constant to avoid repeating '/v1/namespaces/default/models':
-import { apiClient } from './client'
+import { apiClient } from './client'
+
+const MODEL_CRD_BASE_PATH = '/v1/namespaces/default/models'
@@
-  async getAllModels(): Promise<ModelListResponse> {
-    return apiClient.get('/v1/namespaces/default/models')
-  },
+  async getAllModels(): Promise<ModelListResponse> {
+    return apiClient.get(MODEL_CRD_BASE_PATH)
+  },
@@
-  async getModel(name: string): Promise<ModelCRD> {
-    return apiClient.get(`/v1/namespaces/default/models/${encodeURIComponent(name)}`)
-  },
+  async getModel(name: string): Promise<ModelCRD> {
+    return apiClient.get(`${MODEL_CRD_BASE_PATH}/${encodeURIComponent(name)}`)
+  },
@@
-  async createModel(model: ModelCRD): Promise<ModelCRD> {
-    return apiClient.post('/v1/namespaces/default/models', model)
-  },
+  async createModel(model: ModelCRD): Promise<ModelCRD> {
+    return apiClient.post(MODEL_CRD_BASE_PATH, model)
+  },
@@
-  async updateModel(name: string, model: ModelCRD): Promise<ModelCRD> {
-    return apiClient.put(`/v1/namespaces/default/models/${encodeURIComponent(name)}`, model)
-  },
+  async updateModel(name: string, model: ModelCRD): Promise<ModelCRD> {
+    return apiClient.put(`${MODEL_CRD_BASE_PATH}/${encodeURIComponent(name)}`, model)
+  },
@@
-  async deleteModel(name: string): Promise<void> {
-    return apiClient.delete(`/v1/namespaces/default/models/${encodeURIComponent(name)}`)
-  },
+  async deleteModel(name: string): Promise<void> {
+    return apiClient.delete(`${MODEL_CRD_BASE_PATH}/${encodeURIComponent(name)}`)
+  },

This keeps the versioned CRD prefix in exactly one place if/when you need to support multiple namespaces or API versions later.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6599305 and c988717.

📒 Files selected for processing (1)
  • frontend/src/apis/models.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: TypeScript strict mode must be enabled in Frontend
Use functional components with hooks in React
Use Prettier formatter in Frontend with single quotes and no semicolons
Use ESLint with Next.js configuration in Frontend
Use const over let, never use var in TypeScript
Component names must use PascalCase; file names must use kebab-case in Frontend
Use mobile-first responsive design approach in Frontend components
Frontend design: Low saturation + low contrast for reduced eye strain with generous whitespace
Frontend design: Minimal shadows, subtle component differentiation (<10% background variance)
Frontend design: Use mint blue (#14B8A6) as primary accent sparingly
Use Tailwind CSS variables for colors: --color-bg-base, --color-text-primary, --color-border, etc.
Frontend spacing: Use standard units - p-2 (8px), p-4 (16px), p-6 (24px), gap-3 (12px)
Frontend border-radius: rounded-2xl (16px) for large containers, rounded-lg (12px) for cards, rounded-md (6px) for buttons
Frontend typography: H1 text-xl font-semibold, H2 text-lg font-semibold, H3 text-base font-medium, Body text-sm
Use shadcn/ui Button component with variants: default, secondary, ghost, outline, link
Use shadcn/ui Form component with react-hook-form + zod validation

Files:

  • frontend/src/apis/models.ts
frontend/**

📄 CodeRabbit inference engine (AGENTS.md)

Frontend: Only use NEXT_PUBLIC_* for client-safe environment variables

Files:

  • frontend/src/apis/models.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.573Z
Learning: Use Kubernetes-inspired CRD design (Ghost, Model, Shell, Bot, Team, Task)
📚 Learning: 2025-11-26T07:34:32.573Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.573Z
Learning: Use Kubernetes-inspired CRD design (Ghost, Model, Shell, Bot, Team, Task)

Applied to files:

  • frontend/src/apis/models.ts
🧬 Code graph analysis (1)
frontend/src/apis/models.ts (1)
frontend/src/apis/client.ts (1)
  • apiClient (105-105)
🔇 Additional comments (2)
frontend/src/apis/models.ts (2)

7-33: ModelCRD and list response align well with CRD conventions

The ModelCRD shape (metadata/spec/status) and ModelListResponse wrapper look consistent with the Kubernetes‑style CRD pattern you’re using elsewhere (Ghost, Model, Shell, Bot, Team, Task). This should make it straightforward to share generic CRD utilities across resources; no changes needed here.

Based on learnings, this matches the intended CRD design.


44-65: Connection and compatibility types look coherent with the described APIs

TestConnectionRequest/Response and CompatibleModel(s) map cleanly onto the new /models/test-connection and /models/compatible endpoints described in the PR summary. The shapes are minimal but sufficient for the UI use cases (feedback message + list of compatible names), and the snake_case fields on the request side match typical backend JSON conventions.

qdaxb and others added 6 commits November 28, 2025 00:00
- Add 'solo' option to team collaboration mode selection
- Implement simplified UI for solo mode with single bot selector
- Hide Transfer component and team prompts link in solo mode
- Add i18n support for solo mode in both Chinese and English
- Add solo.png placeholder image for mode illustration
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/services/adapters/bot_kinds.py (1)

471-489: Prevent accidental mutation/deletion of PublicModel or shared user models in update_with_user

The agent_config branch in update_with_user (lines 549-633) assumes any non-None model returned by _get_bot_components is a private, bot-dedicated Kind(Model). However, _get_bot_components explicitly documents (line 36) that "Model can be from kinds table (private) or public_models table," creating two critical vulnerabilities:

  1. Deleting global/public or shared models

    At line 510, the code deletes without type verification:

    if model and model.name != model_name:
        db.delete(model)

    If a bot previously pointed to a PublicModel (which lacks user_id ownership), this will permanently delete that model, affecting all users/bots referencing it.

  2. Mutating global/public models with private config

    At lines 525-530, the code mutates the model's CRD without guarding:

    model_crd = Model.model_validate(model.json)
    model_crd.spec.modelConfig = model_config
    model_crd.spec.isCustomConfig = True
    model_crd.spec.protocol = protocol
    model.json = model_crd.model_dump()

    If model is a PublicModel, this overwrites the shared model with per-bot secrets and flags, corrupting it for all referencing bots/users.

  3. Silently swallowed DB errors

    At lines 665-677, bare except: pass hides real database issues:

    if model and hasattr(model, 'id'):
        try:
            db.refresh(model)
        except Exception:
            pass  # PublicModel may not need refresh

Required fix: Before deletion or mutation, verify model is a dedicated private Kind by checking isinstance(model, Kind) and confirming model.kind == "Model" and model.user_id == user_id. Additionally, replace bare except: pass with logged exception handling to surface DB issues.

♻️ Duplicate comments (2)
backend/app/services/adapters/executor_kinds.py (1)

409-414: Missing user_id filter may cause cross-user model access.

This query doesn't filter by Kind.user_id, unlike the existing model query at lines 329-334 which properly uses Kind.user_id == team.user_id. This could return another user's model configuration when names collide.

                             model_kind = db.query(Kind).filter(
+                                Kind.user_id == team.user_id,
                                 Kind.kind == "Model",
                                 Kind.name == model_name_to_use,
                                 Kind.namespace == "default",
-                                Kind.is_active == True
+                                Kind.is_active
                             ).first()
backend/app/api/endpoints/adapter/models.py (1)

336-341: Use logging.exception and sanitize error messages.

Per static analysis and best practices:

     except Exception as e:
-        logger.error(f"Model connection test failed: {str(e)}")
+        logger.exception("Model connection test failed")
         return {
             "success": False,
-            "message": f"Connection failed: {str(e)}"
+            "message": "Connection failed. Check logs for details."
         }

Returning raw exception messages to clients can leak internal details. The previous review also flagged this.

🧹 Nitpick comments (37)
frontend/src/features/settings/components/team-modes/PipelineModeEditor.tsx (1)

11-25: Consider using shared type from types.ts instead of defining props inline.

The types.ts file in the same directory already defines TeamModeEditorProps and related interfaces. This inline interface duplicates the type definition and may diverge over time. Additionally, the toast prop is declared but never used within this component.

Consider either:

  1. Extending from TransferModeEditorProps in types.ts, or
  2. Removing the unused toast prop from the interface if it's not needed.
frontend/src/features/settings/components/team-modes/BotTransfer.tsx (1)

228-241: Add accessible labels to icon buttons.

The Edit and Copy icons function as interactive buttons but lack accessible labels. Screen reader users won't understand their purpose.

                 <Edit
                   className="ml-2 h-4 w-4 text-muted-foreground hover:text-foreground cursor-pointer"
+                  aria-label={t('common.edit')}
+                  role="button"
+                  tabIndex={0}
                   onClick={e => {
                     e.stopPropagation();
                     onEditBot(Number(item.key));
                   }}
                 />
                 <Copy
                   className="ml-3 h-4 w-4 text-muted-foreground hover:text-foreground cursor-pointer"
+                  aria-label={t('common.copy')}
+                  role="button"
+                  tabIndex={0}
                   onClick={e => {
                     e.stopPropagation();
                     onCloneBot(Number(item.key));
                   }}
                 />
frontend/src/features/settings/components/team-modes/LeaderModeEditor.tsx (2)

25-42: Unused toast prop in interface.

The toast prop is declared in LeaderModeEditorProps but is not used within the component. Consider removing it to keep the interface clean, or add the intended usage.

 export interface LeaderModeEditorProps {
   bots: Bot[];
   selectedBotKeys: React.Key[];
   setSelectedBotKeys: React.Dispatch<React.SetStateAction<React.Key[]>>;
   leaderBotId: number | null;
   setLeaderBotId: React.Dispatch<React.SetStateAction<number | null>>;
   unsavedPrompts: Record<string, string>;
   teamPromptMap: Map<number, boolean>;
   isDifyLeader: boolean;
   selectedAgentName: string | null;
   leaderOptions: Bot[];
-  toast: ReturnType<typeof import('@/hooks/use-toast').useToast>['toast'];
   onEditBot: (botId: number) => void;
   onCreateBot: () => void;
   onCloneBot: (botId: number) => void;
   onOpenPromptDrawer: () => void;
   onLeaderChange: (botId: number) => void;
 }

79-88: Optimize repeated bot lookup.

bots.find(b => b.id === leaderBotId) is called twice in adjacent lines. Consider memoizing the selected leader bot to avoid redundant iterations.

+  const selectedLeader = useMemo(
+    () => bots.find(b => b.id === leaderBotId),
+    [bots, leaderBotId]
+  );
+
   return (
     <div className="rounded-md border border-border bg-base p-4 flex flex-col flex-1 min-h-0">
       {/* Leader select */}
       ...
               {leaderBotId !== null ? (
                 <div className="flex items-center justify-between w-full">
                   <div className="flex items-center gap-2 flex-1 min-w-0">
                     <RiRobot2Line className="w-4 h-4 text-text-muted flex-shrink-0" />
                     <span className="truncate max-w-[200px]">
-                      {bots.find(b => b.id === leaderBotId)?.name || ''}
+                      {selectedLeader?.name || ''}
                       <span className="text-text-muted text-xs ml-1">
-                        ({bots.find(b => b.id === leaderBotId)?.agent_name || ''})
+                        ({selectedLeader?.agent_name || ''})
                       </span>
                     </span>
                   </div>
frontend/src/apis/models.ts (2)

7-107: Consider moving type definitions to src/types/ directory.

Per coding guidelines, TypeScript types should be defined in the src/types/ directory. These interfaces (ModelCRD, UnifiedModel, TestConnectionRequest, etc.) could be extracted to a dedicated types file (e.g., src/types/models.ts) and imported here.

This is a recommended refactor for better organization and reusability across the codebase. Based on learnings from coding guidelines.


109-110: Remove duplicate comment.

The // Model Services comment appears twice consecutively.

-// Model Services
 // Model Services
 export const modelApis = {
backend/app/schemas/kind.py (1)

62-63: Consider adding validation for required protocol when isCustomConfig is true.

The comment states protocol is "Required for custom configs", but the field is Optional[str]. If this requirement should be enforced, consider using a Pydantic model validator:

from pydantic import model_validator

class ModelSpec(BaseModel):
    """Model specification"""
    modelConfig: Dict[str, Any]
    isCustomConfig: Optional[bool] = None
    protocol: Optional[str] = None  # Model protocol type: 'openai', 'claude', etc.

    @model_validator(mode='after')
    def validate_protocol_for_custom(self) -> 'ModelSpec':
        if self.isCustomConfig and not self.protocol:
            raise ValueError('protocol is required when isCustomConfig is True')
        return self

Alternatively, update the comment to clarify the field is optional but recommended for custom configs.

frontend/src/features/tasks/service/messageService.ts (1)

20-21: LGTM!

The new model_id and force_override_bot_model parameters correctly extend the message service to support task-level model overrides, aligning with the CreateTaskRequest interface.

Minor nit: Lines 65-66 could use ES6 shorthand property syntax:

-    model_id: model_id,
-    force_override_bot_model: force_override_bot_model,
+    model_id,
+    force_override_bot_model,

Also applies to: 65-66

frontend/src/features/settings/components/ModelList.tsx (3)

82-84: Remove duplicate comment.

There's a duplicate comment on lines 82-83.

-  // Convert to unified display format
   // Convert to unified display format
   const displayModels: DisplayModel[] = React.useMemo(() => {

131-131: Provider type detection may silently default to 'anthropic' for unknown providers.

The ternary env.model === 'openai' ? 'openai' : 'anthropic' assumes all non-OpenAI models are Anthropic. If other providers are added in the future (e.g., 'claude', 'gemini'), this could cause incorrect test connection requests.

Consider explicit provider handling:

-        provider_type: env.model === 'openai' ? 'openai' : 'anthropic',
+        provider_type: env.model === 'openai' ? 'openai' : env.model === 'claude' ? 'anthropic' : 'anthropic',

Or validate and warn for unsupported providers before making the API call.


160-177: Consider adding loading state to prevent double-click during delete.

The handleDelete function doesn't disable the button while the delete operation is in progress, allowing potential duplicate requests if the user clicks multiple times.

+ const [isDeleting, setIsDeleting] = useState(false);

  const handleDelete = async () => {
    if (!deleteConfirmModel) return;
+   setIsDeleting(true);

    try {
      await modelApis.deleteModel(deleteConfirmModel.metadata.name);
      // ...
    } catch (error) {
      // ...
    } finally {
+     setIsDeleting(false);
    }
  };

  // In AlertDialogAction:
- <AlertDialogAction onClick={handleDelete} className="bg-error hover:bg-error/90">
+ <AlertDialogAction onClick={handleDelete} disabled={isDeleting} className="bg-error hover:bg-error/90">
+   {isDeleting ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : null}
    {t('actions.delete')}
  </AlertDialogAction>
backend/app/services/kind_impl.py (1)

58-61: Fix f-string and use logging.exception for better error diagnostics.

The f-string on line 58 has no placeholders, and logger.error should be logger.exception to capture the stack trace when re-raising.

-                            logger.info(f"Encrypted API key for Model resource")
+                            logger.info("Encrypted API key for Model resource")
         except Exception as e:
-            logger.error(f"Failed to encrypt API key: {str(e)}")
+            logger.exception("Failed to encrypt API key")
             raise
frontend/src/app/settings/page.tsx (1)

255-256: Stale comment references Bot instead of Model.

The comment mentions "Bot Creation Modal" but this tab now displays ModelList. Update for clarity.

-      {/* No Bot Creation Modal needed here as it's now part of the BotList component */}
+      {/* Model management is handled within the ModelList component */}
backend/app/services/adapters/executor_kinds.py (1)

445-448: Use logger.exception to capture stack trace on resolution failure.

When model resolution fails, the stack trace would help diagnose issues in production.

                 except Exception as e:
-                    logger.error(f"Failed to resolve model config: {e}")
+                    logger.exception("Failed to resolve model config")
                     # On any error, fallback to original agent_config
                     agent_config_data = agent_config
backend/app/services/adapters/public_model.py (2)

238-248: Avoid bare except and clarify function purpose.

The bare except: catches all exceptions including KeyboardInterrupt. Also, get_model_provider returns env.get("model") which appears to return a model name (e.g., "claude") rather than a provider—consider renaming for clarity.

         def get_model_provider(json_data: Optional[Dict]) -> Optional[str]:
+            """Extract model protocol/provider from model config (e.g., 'openai', 'claude')"""
             if not isinstance(json_data, dict):
                 return None
             try:
                 model_crd = Model.model_validate(json_data)
                 env = model_crd.spec.modelConfig.get("env", {})
                 if not isinstance(env, dict):
                     return None
                 return env.get("model")
-            except:
+            except (ValueError, KeyError, AttributeError):
                 return None

250-258: Replace bare except with specific exception types.

         def get_model_display_name(json_data: Optional[Dict]) -> Optional[str]:
             """Extract displayName from model metadata"""
             if not isinstance(json_data, dict):
                 return None
             try:
                 model_crd = Model.model_validate(json_data)
                 return model_crd.metadata.displayName
-            except:
+            except (ValueError, KeyError, AttributeError):
                 return None
frontend/src/features/settings/components/ModelEdit.tsx (1)

73-85: Potential issue with modelOptions in dependency array.

The modelOptions variable changes when providerType changes. Including it in the dependency array causes this effect to re-run when the provider switches, which may unintentionally reset the model selection state. Consider using a ref or checking if model has changed to avoid re-running logic meant only for initialization.

+  const modelOptionsRef = useRef(modelOptions);
+  modelOptionsRef.current = modelOptions;
+
   useEffect(() => {
     if (model?.spec.modelConfig?.env?.model_id) {
       const id = model.spec.modelConfig.env.model_id;
-      const isPreset = modelOptions.some(opt => opt.value === id && opt.value !== 'custom');
+      const isPreset = modelOptionsRef.current.some(opt => opt.value === id && opt.value !== 'custom');
       if (isPreset) {
         setModelId(id);
         setCustomModelId('');
       } else {
         setModelId('custom');
         setCustomModelId(id);
       }
     }
-  }, [model, modelOptions]);
+  }, [model]);
frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx (2)

37-46: Unused prop editingTeam in destructuring.

The editingTeam prop is defined in SoloModeEditorProps (line 30) and passed to the component but never used in the function body.

 export default function SoloModeEditor({
   bots,
   setBots,
   selectedBotId,
   setSelectedBotId,
-  editingTeam,
   toast,
   unsavedPrompts = {},
   teamPromptMap,
   onOpenPromptDrawer,
 }: SoloModeEditorProps) {

Also remove from interface if not needed:

 export interface SoloModeEditorProps {
   bots: Bot[];
   setBots: React.Dispatch<React.SetStateAction<Bot[]>>;
   selectedBotId: number | null;
   setSelectedBotId: React.Dispatch<React.SetStateAction<number | null>>;
-  editingTeam: Team | null;
   toast: ReturnType<typeof import('@/hooks/use-toast').useToast>['toast'];

127-139: Reliance on bot array ordering may be fragile.

The logic assumes the newest bot is at bots[0], but this depends on how the parent component sorts the bots array. Consider finding the bot with the highest ID or most recent created_at instead for more reliable selection after creation.

frontend/src/features/tasks/components/ChatArea.tsx (1)

520-529: Consider extracting duplicated ModelSelector rendering.

The ModelSelector is rendered identically in two places (lines 520-529 and 660-669). Consider extracting this into a reusable element or variable to reduce duplication and ensure consistency.

const modelSelectorElement = selectedTeam && (
  <ModelSelector
    selectedModel={selectedModel}
    setSelectedModel={setSelectedModel}
    forceOverride={forceOverride}
    setForceOverride={setForceOverride}
    selectedTeam={selectedTeam}
    disabled={hasMessages || isLoading}
  />
);

// Then use {modelSelectorElement} in both locations

Also applies to: 660-669

backend/app/services/adapters/team_kinds.py (3)

852-852: Remove unnecessary f-string prefix.

The string at line 852 has an f prefix but contains no placeholders. Remove the f prefix.

-                logger.info(f"[_get_bot_summary] Custom config (isCustomConfig=True), returning empty agent_config")
+                logger.info("[_get_bot_summary] Custom config (isCustomConfig=True), returning empty agent_config")

Note: Same issue exists at line 936 in the duplicate method which should be removed.


802-803: Use module-level logger instead of creating new ones inside methods.

The method creates a new logger inside the function, but a module-level logger already exists at line 11. Reuse the existing logger for consistency.

     def _get_bot_summary(self, bot: Kind, db: Session, user_id: int) -> Dict[str, Any]:
         """
         Get a summary of bot information including agent_config with only necessary fields.
         This is used for team list to determine if bots have predefined models.
         """
-        import logging
-        logger = logging.getLogger(__name__)
-        
         from app.models.public_model import PublicModel

The module-level logger at line 11 will be used automatically.


749-780: Unnecessary re-query for first bot.

The code queries first_bot by ID (lines 751-756), but this bot was already fetched in the loop above when processing members. Consider storing the first bot during iteration to avoid the extra database query.

         bots = []
         agent_names = set()
+        first_bot_data = None  # Store first bot for agent_type calculation
         
         for member in team_crd.spec.members:
             # Find bot in kinds table
             bot = db.query(Kind).filter(
                 Kind.user_id == user_id,
                 Kind.kind == "Bot",
                 Kind.name == member.botRef.name,
                 Kind.namespace == member.botRef.namespace,
                 Kind.is_active == True
             ).first()
             
             if bot:
+                if first_bot_data is None:
+                    first_bot_data = bot
                 bot_summary = self._get_bot_summary(bot, db, user_id)
                 # ... rest of loop ...
         
         # Get agent_type from the first bot's shell
         agent_type = None
-        if bots:
-            first_bot_id = bots[0]["bot_id"]
-            first_bot = db.query(Kind).filter(
-                Kind.id == first_bot_id,
-                Kind.user_id == user_id,
-                Kind.kind == "Bot",
-                Kind.is_active == True
-            ).first()
-            
-            if first_bot:
+        if first_bot_data:
+            bot_crd = Bot.model_validate(first_bot_data.json)
shared/utils/crypto.py (2)

80-82: Use logging.exception for better error diagnostics.

When logging exceptions, logging.exception automatically includes the stack trace, making debugging easier.

     except Exception as e:
-        logger.error(f"Failed to encrypt sensitive data: {str(e)}")
+        logger.exception("Failed to encrypt sensitive data")
         raise

126-129: Silent fallback to original text may hide decryption issues.

Returning the original encrypted text on failure provides backward compatibility but could mask configuration issues (wrong keys). The warning log is appropriate, but consider using logging.exception for full stack trace:

     except Exception as e:
-        logger.warning(f"Failed to decrypt sensitive data: {str(e)}")
+        logger.warning("Failed to decrypt sensitive data: %s", e, exc_info=True)
         # Return the original text as fallback for backward compatibility
         return encrypted_text
frontend/src/features/settings/components/TeamEdit.tsx (1)

607-853: Consider extracting inline CSS to a separate stylesheet or CSS module.

While the dangerouslySetInnerHTML usage here is safe (static CSS strings, no user input), it's a code smell that affects maintainability:

  1. CSS is duplicated/interleaved with JSX
  2. Static analysis tools flag it as potential XSS
  3. Makes the component harder to read

Consider moving these styles to a CSS module or using Tailwind's @apply in a global stylesheet. This would eliminate the security warnings and improve maintainability.

frontend/src/features/tasks/components/ModelSelector.tsx (1)

59-61: Consider using a constant or config file for localStorage keys.

These localStorage keys are defined inline. If they need to be shared or accessed elsewhere, consider extracting to a shared constants file.

backend/app/api/endpoints/adapter/models.py (3)

139-139: Move import to module level.

The HTTPException import should be at the module level with other imports, not inside the function.

-from fastapi import APIRouter, Depends, Query, status
+from fastapi import APIRouter, Depends, HTTPException, Query, status

Then remove Line 139.


304-308: Remove unused response variable assignments.

The response variables are assigned but never used.

-            response = client.chat.completions.create(
+            client.chat.completions.create(
                 model=model_id,
                 messages=[{"role": "user", "content": "hi"}],
                 max_tokens=1
             )

Same for the Anthropic branch at Line 320.

Also applies to: 320-324


344-394: Consider using model_aggregation_service.list_available_models instead.

This endpoint duplicates logic that already exists in model_aggregation_service.list_available_models with agent_name filtering. Consider refactoring to use the existing service:

@router.get("/compatible")
def get_compatible_models(
    agent_name: str = Query(..., description="Agent name (Agno or ClaudeCode)"),
    current_user: User = Depends(security.get_current_user),
    db: Session = Depends(get_db)
):
    models = model_aggregation_service.list_available_models(
        db=db,
        current_user=current_user,
        agent_name=agent_name
    )
    return {"models": [{"name": m["name"]} for m in models]}

This reduces code duplication and ensures consistent filtering logic.

backend/app/services/model_aggregation_service.py (2)

265-270: Consider making the public models limit configurable or paginated.

The hardcoded limit=1000 works for current scale but may become a performance issue as the number of public models grows. Consider making this configurable or implementing proper pagination.

# At class level or module level
PUBLIC_MODELS_FETCH_LIMIT = 1000

358-380: Inefficient public model lookup - fetches all models to find one.

When looking up a public model by name, this fetches up to 1000 models and filters client-side. Consider adding a direct lookup method to public_model_service:

# In public_model_service
def get_by_name(self, db: Session, name: str, current_user: User) -> Optional[Dict]:
    ...

This would be more efficient for single-model lookups.

backend/app/services/adapters/bot_kinds.py (5)

105-195: Model lookup helper works but could be cleaned up (logging & == True style)

The _get_model_by_name_and_type / _get_model_by_name helpers look functionally correct and align with the “user first, then public” resolution strategy.

Two niceties you might want to address:

  • You’re importing logging and building the logger on every call. Consider a module-level logger (logger = logging.getLogger(__name__)) instead, reusing it in this and other helpers.
  • Ruff flags == True comparisons on ORM columns (E712). For SQLAlchemy it’s safer/more explicit to use .is_(True) rather than a direct equality. For the new queries in this helper you could do:
-                Kind.is_active == True
+                Kind.is_active.is_(True)
...
-                PublicModel.is_active == True
+                PublicModel.is_active.is_(True)
...
-                Kind.is_active == True
+                Kind.is_active.is_(True)
...
-                PublicModel.is_active == True
+                PublicModel.is_active.is_(True)

This keeps Ruff happy and avoids any ambiguity around boolean column comparisons.


254-300: Create path correctly wires modelRef, but consider existence checks and encryption usage

The new create_with_user flow for models behaves as intended:

  • Predefined config ({bind_model, bind_model_type?}) → just set modelRef to that name/namespace.
  • Custom config → create a private Model Kind with modelConfig, protocol, and isCustomConfig=True, then point modelRef to it.

Two points worth tightening:

  1. No validation that the referenced bind_model exists

    When _is_predefined_model(obj_in.agent_config) is true, create_with_user never checks that a Kind(Model) or PublicModel with that name/namespace actually exists before committing the Bot. You only attempt to load the model afterward for the response:

    if model is None:
        model = self._get_model_by_name(db, model_ref_name, model_ref_namespace, user_id)

    which can return None without failing the request. That leaves persisted bots with dangling modelRefs if someone POSTs an arbitrary bind_model via the API.

    Consider validating and raising 4xx on create if _get_model_by_name(...) returns None in the predefined branch.

  2. Encrypted config isn’t used when persisting the private Model

    You compute:

    encrypted_agent_config = self._encrypt_agent_config(obj_in.agent_config)

    but the modelConfig for private models is built from obj_in.agent_config instead. If the intention is to encrypt sensitive fields (e.g. keys under env), you likely want:

  •    protocol = self._get_protocol_from_config(obj_in.agent_config)
    
  •    model_config = {k: v for k, v in obj_in.agent_config.items() if k != "protocol"}
    
  •    protocol = self._get_protocol_from_config(encrypted_agent_config)
    
  •    model_config = {
    
  •        k: v
    
  •        for k, v in encrypted_agent_config.items()
    
  •        if k != "protocol"
    
  •    }
    
    
    so that the stored `Model` CRD benefits from the same encryption as other agent configs.
    
    

Both are behavioral changes, so please double-check against how other model-management paths handle validation and encryption before applying.

Also applies to: 345-360, 385-393


742-783: Model resolution in _get_bot_components is correct; ensure legacy bots always have modelRef

Switching _get_bot_components to use _get_model_by_name is a good way to support both private Kind(Model) and PublicModel sources with a single call, and the extra logging will help with debugging.

One thing to verify: this now assumes that every bot CRD has a valid spec.modelRef. For any legacy bots that previously relied on inline agent_config or private_model-style fields, Bot.model_validate(bot.json) followed by bot_crd.spec.modelRef will fail if modelRef is missing. If you still have such data in production, you might need a migration or a fallback path here to keep those bots loadable.


785-852: Batch model resolution is efficient; minor style tweak for PublicModel.is_active

The batched fetch strategy is solid: you avoid N+1 queries, use key sets for Ghost/Shell/Model, and then fill in any missing models from PublicModel. The combined model_map: {(name, namespace): Kind or PublicModel} nicely lines up with how _convert_to_bot_dict treats the model parameter.

Ruff’s E712 warning about PublicModel.is_active == True also applies here; for SQLAlchemy, it’s clearer (and warning-free) to use:

-                public_models = db.query(PublicModel).filter(
-                    PublicModel.is_active == True
-                ).filter(public_model_filter).all()
+                public_models = (
+                    db.query(PublicModel)
+                    .filter(PublicModel.is_active.is_(True))
+                    .filter(public_model_filter)
+                    .all()
+                )

to match the style update you may apply in _get_model_by_name_and_type.


854-945: _convert_to_bot_dict logic matches the new model semantics; tighten type hints per Ruff and guidelines

The expanded _convert_to_bot_dict correctly encodes the various scenarios:

  • override_agent_config → echo {bind_model, bind_model_type} back (predefined).
  • model is PublicModel{bind_model, bind_model_type="public"}.
  • modelRef.name != "{bot.name}-model" → treat as shared user model (bind_model_type="user").
  • Dedicated private model with isCustomConfig=True → full config + protocol.
  • Dedicated private but not custom → bind-by-name for backward compatibility.

Two small improvements:

  1. Make optionals explicit and type model

    Ruff’s RUF013 warning is about implicit Optional. You can also fully type model as “private or public or None” and the override config as optional to align with your code style guide (“type hints required for all Python function signatures”):

  • def _convert_to_bot_dict(self, bot: Kind, ghost: Kind = None, shell: Kind = None, model = None, override_agent_config: Dict[str, Any] = None) -> Dict[str, Any]:
  • def _convert_to_bot_dict(
  •    self,
    
  •    bot: Kind,
    
  •    ghost: Kind | None = None,
    
  •    shell: Kind | None = None,
    
  •    model: Kind | PublicModel | None = None,
    
  •    override_agent_config: Dict[str, Any] | None = None,
    
  • ) -> Dict[str, Any]:

This matches how you actually use the parameters and satisfies Ruff’s Optional guidance.

2. **Reuse a shared logger instead of per-call imports (optional)**

As with the other helpers, you’re creating a logger inside the function. If you move to a module-level logger (`logger = logging.getLogger(__name__)`), this function can just reference it and avoid repeated imports.

Overall the branching looks correct; once the update/delete semantics in `update_with_user` are fixed, this method should give the frontend a consistent view of how each bot is bound to its model.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c988717ad2a0b25ff233f9ac1411da86ce9ff54e and 5227c0bdf8126eee8be212f23db8ad07488e7576.

</details>

<details>
<summary>⛔ Files ignored due to path filters (2)</summary>

* `frontend/package-lock.json` is excluded by `!**/package-lock.json`
* `frontend/public/settings/solo.png` is excluded by `!**/*.png`

</details>

<details>
<summary>📒 Files selected for processing (37)</summary>

* `backend/app/api/endpoints/adapter/bots.py` (1 hunks)
* `backend/app/api/endpoints/adapter/models.py` (4 hunks)
* `backend/app/schemas/kind.py` (2 hunks)
* `backend/app/schemas/team.py` (1 hunks)
* `backend/app/services/adapters/bot_kinds.py` (16 hunks)
* `backend/app/services/adapters/executor_kinds.py` (3 hunks)
* `backend/app/services/adapters/public_model.py` (4 hunks)
* `backend/app/services/adapters/task_kinds.py` (1 hunks)
* `backend/app/services/adapters/team_kinds.py` (3 hunks)
* `backend/app/services/kind_impl.py` (2 hunks)
* `backend/app/services/model_aggregation_service.py` (1 hunks)
* `frontend/package.json` (1 hunks)
* `frontend/src/apis/models.ts` (1 hunks)
* `frontend/src/apis/tasks.ts` (1 hunks)
* `frontend/src/app/settings/page.tsx` (8 hunks)
* `frontend/src/components/ui/alert-dialog.tsx` (1 hunks)
* `frontend/src/features/settings/components/BotEdit.tsx` (25 hunks)
* `frontend/src/features/settings/components/ModelEdit.tsx` (1 hunks)
* `frontend/src/features/settings/components/ModelList.tsx` (1 hunks)
* `frontend/src/features/settings/components/TeamEdit.tsx` (16 hunks)
* `frontend/src/features/settings/components/team-modes/BotTransfer.tsx` (1 hunks)
* `frontend/src/features/settings/components/team-modes/LeaderModeEditor.tsx` (1 hunks)
* `frontend/src/features/settings/components/team-modes/PipelineModeEditor.tsx` (1 hunks)
* `frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx` (1 hunks)
* `frontend/src/features/settings/components/team-modes/index.ts` (1 hunks)
* `frontend/src/features/settings/components/team-modes/types.ts` (1 hunks)
* `frontend/src/features/settings/services/bots.ts` (3 hunks)
* `frontend/src/features/tasks/components/ChatArea.tsx` (7 hunks)
* `frontend/src/features/tasks/components/ClarificationForm.tsx` (1 hunks)
* `frontend/src/features/tasks/components/ModelSelector.tsx` (1 hunks)
* `frontend/src/features/tasks/components/TeamSelector.tsx` (1 hunks)
* `frontend/src/features/tasks/service/messageService.ts` (4 hunks)
* `frontend/src/i18n/locales/en/common.json` (6 hunks)
* `frontend/src/i18n/locales/zh-CN/common.json` (6 hunks)
* `frontend/src/types/api.ts` (1 hunks)
* `shared/tests/utils/test_crypto.py` (2 hunks)
* `shared/utils/crypto.py` (4 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (5)</summary>

<details>
<summary>frontend/**/*.{ts,tsx}</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> `frontend/**/*.{ts,tsx}`: TypeScript strict mode must be enabled in Frontend
> Use functional components with hooks in React
> Use Prettier formatter in Frontend with single quotes and no semicolons
> Use ESLint with Next.js configuration in Frontend
> Use `const` over `let`, never use `var` in TypeScript
> Component names must use PascalCase; file names must use kebab-case in Frontend
> Use mobile-first responsive design approach in Frontend components
> Frontend design: Low saturation + low contrast for reduced eye strain with generous whitespace
> Frontend design: Minimal shadows, subtle component differentiation (<10% background variance)
> Frontend design: Use mint blue (#14B8A6) as primary accent sparingly
> Use Tailwind CSS variables for colors: --color-bg-base, --color-text-primary, --color-border, etc.
> Frontend spacing: Use standard units - p-2 (8px), p-4 (16px), p-6 (24px), gap-3 (12px)
> Frontend border-radius: rounded-2xl (16px) for large containers, rounded-lg (12px) for cards, rounded-md (6px) for buttons
> Frontend typography: H1 text-xl font-semibold, H2 text-lg font-semibold, H3 text-base font-medium, Body text-sm
> Use shadcn/ui Button component with variants: default, secondary, ghost, outline, link
> Use shadcn/ui Form component with react-hook-form + zod validation

Files:
- `frontend/src/features/settings/components/team-modes/PipelineModeEditor.tsx`
- `frontend/src/apis/tasks.ts`
- `frontend/src/features/tasks/components/ClarificationForm.tsx`
- `frontend/src/features/settings/components/team-modes/types.ts`
- `frontend/src/features/tasks/components/TeamSelector.tsx`
- `frontend/src/features/settings/components/team-modes/BotTransfer.tsx`
- `frontend/src/types/api.ts`
- `frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx`
- `frontend/src/features/settings/components/team-modes/index.ts`
- `frontend/src/features/settings/components/ModelEdit.tsx`
- `frontend/src/features/settings/components/team-modes/LeaderModeEditor.tsx`
- `frontend/src/features/settings/components/BotEdit.tsx`
- `frontend/src/features/tasks/service/messageService.ts`
- `frontend/src/features/settings/components/ModelList.tsx`
- `frontend/src/features/tasks/components/ModelSelector.tsx`
- `frontend/src/features/tasks/components/ChatArea.tsx`
- `frontend/src/components/ui/alert-dialog.tsx`
- `frontend/src/features/settings/services/bots.ts`
- `frontend/src/features/settings/components/TeamEdit.tsx`
- `frontend/src/app/settings/page.tsx`
- `frontend/src/apis/models.ts`

</details>
<details>
<summary>frontend/**</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> Frontend: Only use `NEXT_PUBLIC_*` for client-safe environment variables

Files:
- `frontend/src/features/settings/components/team-modes/PipelineModeEditor.tsx`
- `frontend/src/apis/tasks.ts`
- `frontend/src/features/tasks/components/ClarificationForm.tsx`
- `frontend/src/features/settings/components/team-modes/types.ts`
- `frontend/package.json`
- `frontend/src/features/tasks/components/TeamSelector.tsx`
- `frontend/src/features/settings/components/team-modes/BotTransfer.tsx`
- `frontend/src/types/api.ts`
- `frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx`
- `frontend/src/features/settings/components/team-modes/index.ts`
- `frontend/src/features/settings/components/ModelEdit.tsx`
- `frontend/src/features/settings/components/team-modes/LeaderModeEditor.tsx`
- `frontend/src/features/settings/components/BotEdit.tsx`
- `frontend/src/features/tasks/service/messageService.ts`
- `frontend/src/features/settings/components/ModelList.tsx`
- `frontend/src/features/tasks/components/ModelSelector.tsx`
- `frontend/src/features/tasks/components/ChatArea.tsx`
- `frontend/src/components/ui/alert-dialog.tsx`
- `frontend/src/i18n/locales/en/common.json`
- `frontend/src/features/settings/services/bots.ts`
- `frontend/src/features/settings/components/TeamEdit.tsx`
- `frontend/src/app/settings/page.tsx`
- `frontend/src/apis/models.ts`
- `frontend/src/i18n/locales/zh-CN/common.json`

</details>
<details>
<summary>**/*.py</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> `**/*.py`: Python code must be PEP 8 compliant with Black formatter (line length: 88)
> Use isort for import organization in Python
> Python functions and variables must have descriptive names
> Docstrings required for all public Python functions and classes
> Extract magic numbers to named constants in Python
> Python functions must not exceed 50 lines
> Type hints required for all Python function signatures
> Run pylint and flake8 for Python code quality checks

Files:
- `backend/app/schemas/kind.py`
- `backend/app/api/endpoints/adapter/bots.py`
- `backend/app/services/kind_impl.py`
- `backend/app/schemas/team.py`
- `backend/app/services/adapters/task_kinds.py`
- `shared/utils/crypto.py`
- `backend/app/services/adapters/team_kinds.py`
- `backend/app/services/adapters/public_model.py`
- `backend/app/api/endpoints/adapter/models.py`
- `backend/app/services/adapters/executor_kinds.py`
- `backend/app/services/adapters/bot_kinds.py`
- `backend/app/services/model_aggregation_service.py`
- `shared/tests/utils/test_crypto.py`

</details>
<details>
<summary>frontend/src/types/**/*.{ts,tsx}</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> Define TypeScript types in `src/types/` directory in Frontend

Files:
- `frontend/src/types/api.ts`

</details>
<details>
<summary>**/test_*.py</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> `**/test_*.py`: Follow AAA pattern in tests: Arrange, Act, Assert
> Mock external services (Anthropic, OpenAI, Docker, APIs) in Python tests
> Use descriptive test names explaining what is tested in Python tests
> Test edge cases and error conditions in Python tests
> Keep Python tests independent and isolated

Files:
- `shared/tests/utils/test_crypto.py`

</details>

</details><details>
<summary>🧠 Learnings (9)</summary>

<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/src/types/**/*.{ts,tsx} : Define TypeScript types in src/types/ directory in Frontend


**Applied to files:**
- `frontend/src/features/settings/components/team-modes/types.ts`
- `frontend/src/features/settings/components/team-modes/index.ts`
- `frontend/src/apis/models.ts`

</details>
<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : TypeScript strict mode must be enabled in Frontend


**Applied to files:**
- `frontend/src/features/settings/components/team-modes/types.ts`

</details>
<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use shadcn/ui Form component with react-hook-form + zod validation


**Applied to files:**
- `frontend/package.json`

</details>
<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use mobile-first responsive design approach in Frontend components


**Applied to files:**
- `frontend/src/features/tasks/components/TeamSelector.tsx`
- `frontend/src/features/settings/components/TeamEdit.tsx`

</details>
<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Frontend design: Minimal shadows, subtle component differentiation (<10% background variance)


**Applied to files:**
- `frontend/src/features/tasks/components/TeamSelector.tsx`
- `frontend/src/components/ui/alert-dialog.tsx`

</details>
<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Frontend border-radius: rounded-2xl (16px) for large containers, rounded-lg (12px) for cards, rounded-md (6px) for buttons


**Applied to files:**
- `frontend/src/features/tasks/components/TeamSelector.tsx`

</details>
<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Frontend design: Low saturation + low contrast for reduced eye strain with generous whitespace


**Applied to files:**
- `frontend/src/features/tasks/components/TeamSelector.tsx`

</details>
<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use functional components with hooks in React


**Applied to files:**
- `frontend/src/features/settings/components/BotEdit.tsx`
- `frontend/src/features/settings/components/TeamEdit.tsx`

</details>
<details>
<summary>📚 Learning: 2025-11-26T07:34:32.591Z</summary>

Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use shadcn/ui Button component with variants: default, secondary, ghost, outline, link


**Applied to files:**
- `frontend/src/features/settings/components/BotEdit.tsx`
- `frontend/src/components/ui/alert-dialog.tsx`

</details>

</details><details>
<summary>🧬 Code graph analysis (14)</summary>

<details>
<summary>frontend/src/features/settings/components/team-modes/PipelineModeEditor.tsx (1)</summary><blockquote>

<details>
<summary>frontend/src/features/settings/components/team-modes/BotTransfer.tsx (1)</summary>

* `BotTransfer` (38-267)

</details>

</blockquote></details>
<details>
<summary>frontend/src/features/settings/components/team-modes/types.ts (2)</summary><blockquote>

<details>
<summary>frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx (1)</summary>

* `SoloModeEditorProps` (25-35)

</details>
<details>
<summary>frontend/src/features/settings/components/team-modes/LeaderModeEditor.tsx (1)</summary>

* `LeaderModeEditorProps` (25-42)

</details>

</blockquote></details>
<details>
<summary>frontend/src/types/api.ts (1)</summary><blockquote>

<details>
<summary>backend/app/schemas/team.py (1)</summary>

* `BotSummary` (13-16)

</details>

</blockquote></details>
<details>
<summary>frontend/src/features/settings/components/team-modes/LeaderModeEditor.tsx (2)</summary><blockquote>

<details>
<summary>frontend/src/utils/styles.ts (1)</summary>

* `getPromptBadgeStyle` (44-74)

</details>
<details>
<summary>frontend/src/features/settings/components/team-modes/BotTransfer.tsx (1)</summary>

* `BotTransfer` (38-267)

</details>

</blockquote></details>
<details>
<summary>frontend/src/features/tasks/service/messageService.ts (1)</summary><blockquote>

<details>
<summary>frontend/src/apis/tasks.ts (1)</summary>

* `CreateTaskRequest` (9-24)

</details>

</blockquote></details>
<details>
<summary>frontend/src/features/tasks/components/ChatArea.tsx (4)</summary><blockquote>

<details>
<summary>backend/app/schemas/kind.py (1)</summary>

* `Model` (71-77)

</details>
<details>
<summary>frontend/src/apis/models.ts (1)</summary>

* `Model` (61-64)

</details>
<details>
<summary>frontend/src/features/tasks/components/ModelSelector.tsx (3)</summary>

* `Model` (29-34)
* `DEFAULT_MODEL_NAME` (37-37)
* `ModelSelector` (92-420)

</details>
<details>
<summary>frontend/src/features/tasks/service/messageService.ts (1)</summary>

* `sendMessage` (13-75)

</details>

</blockquote></details>
<details>
<summary>backend/app/api/endpoints/adapter/models.py (3)</summary><blockquote>

<details>
<summary>backend/app/models/kind.py (1)</summary>

* `Kind` (14-30)

</details>
<details>
<summary>backend/app/services/model_aggregation_service.py (3)</summary>

* `ModelType` (29-37)
* `list_available_models` (196-312)
* `resolve_model` (384-421)

</details>
<details>
<summary>backend/app/services/adapters/public_model.py (1)</summary>

* `list_model_names` (201-298)

</details>

</blockquote></details>
<details>
<summary>backend/app/services/adapters/executor_kinds.py (3)</summary><blockquote>

<details>
<summary>shared/utils/crypto.py (1)</summary>

* `decrypt_api_key` (222-239)

</details>
<details>
<summary>backend/app/models/kind.py (1)</summary>

* `Kind` (14-30)

</details>
<details>
<summary>backend/app/schemas/kind.py (1)</summary>

* `Model` (71-77)

</details>

</blockquote></details>
<details>
<summary>backend/app/services/adapters/bot_kinds.py (2)</summary><blockquote>

<details>
<summary>backend/app/models/kind.py (1)</summary>

* `Kind` (14-30)

</details>
<details>
<summary>backend/app/schemas/kind.py (2)</summary>

* `Bot` (146-152)
* `Model` (71-77)

</details>

</blockquote></details>
<details>
<summary>frontend/src/components/ui/alert-dialog.tsx (2)</summary><blockquote>

<details>
<summary>frontend/src/lib/utils.ts (1)</summary>

* `cn` (8-10)

</details>
<details>
<summary>frontend/src/components/ui/button.tsx (1)</summary>

* `buttonVariants` (55-55)

</details>

</blockquote></details>
<details>
<summary>frontend/src/features/settings/components/TeamEdit.tsx (4)</summary><blockquote>

<details>
<summary>frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx (1)</summary>

* `SoloModeEditor` (37-277)

</details>
<details>
<summary>frontend/src/features/settings/components/team-modes/PipelineModeEditor.tsx (1)</summary>

* `PipelineModeEditor` (27-60)

</details>
<details>
<summary>frontend/src/features/settings/components/team-modes/LeaderModeEditor.tsx (1)</summary>

* `LeaderModeEditor` (44-206)

</details>
<details>
<summary>frontend/src/components/ui/dialog.tsx (6)</summary>

* `Dialog` (92-92)
* `DialogContent` (97-97)
* `DialogHeader` (98-98)
* `DialogTitle` (100-100)
* `DialogDescription` (101-101)
* `DialogFooter` (99-99)

</details>

</blockquote></details>
<details>
<summary>frontend/src/app/settings/page.tsx (1)</summary><blockquote>

<details>
<summary>backend/app/schemas/kind.py (1)</summary>

* `ModelList` (80-84)

</details>

</blockquote></details>
<details>
<summary>frontend/src/apis/models.ts (4)</summary><blockquote>

<details>
<summary>backend/app/schemas/kind.py (1)</summary>

* `Model` (71-77)

</details>
<details>
<summary>frontend/src/features/tasks/components/ModelSelector.tsx (1)</summary>

* `Model` (29-34)

</details>
<details>
<summary>backend/app/services/model_aggregation_service.py (1)</summary>

* `UnifiedModel` (40-92)

</details>
<details>
<summary>frontend/src/apis/client.ts (1)</summary>

* `apiClient` (105-105)

</details>

</blockquote></details>
<details>
<summary>shared/tests/utils/test_crypto.py (1)</summary><blockquote>

<details>
<summary>shared/utils/crypto.py (10)</summary>

* `is_token_encrypted` (185-195)
* `encrypt_api_key` (202-219)
* `decrypt_api_key` (222-239)
* `is_api_key_encrypted` (242-261)
* `mask_api_key` (264-285)
* `encrypt_sensitive_data` (42-82)
* `decrypt_sensitive_data` (85-129)
* `is_data_encrypted` (132-152)
* `encrypt_git_token` (159-169)
* `decrypt_git_token` (172-182)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ast-grep (0.40.0)</summary>

<details>
<summary>frontend/src/features/settings/components/TeamEdit.tsx</summary>

[warning] 608-608: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
    - https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
    - https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

---

[warning] 856-856: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
    - https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
    - https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

</details>

</details>
<details>
<summary>🪛 Biome (2.1.2)</summary>

<details>
<summary>frontend/src/features/settings/components/TeamEdit.tsx</summary>

[error] 609-609: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

---

[error] 857-857: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

</details>

</details>
<details>
<summary>🪛 Ruff (0.14.6)</summary>

<details>
<summary>backend/app/services/kind_impl.py</summary>

58-58: f-string without any placeholders

Remove extraneous `f` prefix

(F541)

---

60-60: Use `logging.exception` instead of `logging.error`

Replace with `exception`

(TRY400)

---

60-60: Use explicit conversion flag

Replace with conversion flag

(RUF010)

---

78-78: Do not catch blind exception: `Exception`

(BLE001)

---

79-79: Use explicit conversion flag

Replace with conversion flag

(RUF010)

</details>
<details>
<summary>shared/utils/crypto.py</summary>

81-81: Use `logging.exception` instead of `logging.error`

Replace with `exception`

(TRY400)

---

81-81: Use explicit conversion flag

Replace with conversion flag

(RUF010)

---

127-127: Use explicit conversion flag

Replace with conversion flag

(RUF010)

---

151-151: Do not catch blind exception: `Exception`

(BLE001)

</details>
<details>
<summary>backend/app/services/adapters/team_kinds.py</summary>

755-755: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

765-765: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

817-817: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

835-835: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

852-852: f-string without any placeholders

Remove extraneous `f` prefix

(F541)

---

862-862: Avoid equality comparisons to `True`; use `PublicModel.is_active:` for truth checks

Replace with `PublicModel.is_active`

(E712)

---

901-901: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

919-919: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

936-936: f-string without any placeholders

Remove extraneous `f` prefix

(F541)

---

946-946: Avoid equality comparisons to `True`; use `PublicModel.is_active:` for truth checks

Replace with `PublicModel.is_active`

(E712)

</details>
<details>
<summary>backend/app/services/adapters/public_model.py</summary>

256-256: Consider moving this statement to an `else` block

(TRY300)

---

257-257: Do not use bare `except`

(E722)

---

280-280: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

289-289: Avoid equality comparisons to `True`; use `PublicModel.is_active:` for truth checks

Replace with `PublicModel.is_active`

(E712)

</details>
<details>
<summary>backend/app/api/endpoints/adapter/models.py</summary>

70-70: Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

---

71-71: Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

---

113-113: Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

---

114-114: Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

---

266-266: Unused function argument: `current_user`

(ARG001)

---

266-266: Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

---

320-320: Local variable `response` is assigned to but never used

Remove assignment to unused variable `response`

(F841)

---

336-336: Do not catch blind exception: `Exception`

(BLE001)

---

337-337: Use `logging.exception` instead of `logging.error`

Replace with `exception`

(TRY400)

---

337-337: Use explicit conversion flag

Replace with conversion flag

(RUF010)

---

340-340: Use explicit conversion flag

Replace with conversion flag

(RUF010)

---

347-347: Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

---

348-348: Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

---

371-371: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

391-391: Do not catch blind exception: `Exception`

(BLE001)

</details>
<details>
<summary>backend/app/services/adapters/executor_kinds.py</summary>

413-413: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

426-426: Do not catch blind exception: `Exception`

(BLE001)

---

445-445: Do not catch blind exception: `Exception`

(BLE001)

---

446-446: Use `logging.exception` instead of `logging.error`

Replace with `exception`

(TRY400)

</details>
<details>
<summary>backend/app/services/adapters/bot_kinds.py</summary>

138-138: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

151-151: Avoid equality comparisons to `True`; use `PublicModel.is_active:` for truth checks

Replace with `PublicModel.is_active`

(E712)

---

166-166: Avoid equality comparisons to `True`; use `Kind.is_active:` for truth checks

Replace with `Kind.is_active`

(E712)

---

177-177: Avoid equality comparisons to `True`; use `PublicModel.is_active:` for truth checks

Replace with `PublicModel.is_active`

(E712)

---

577-577: f-string without any placeholders

Remove extraneous `f` prefix

(F541)

---

593-593: f-string without any placeholders

Remove extraneous `f` prefix

(F541)

---

676-677: `try`-`except`-`pass` detected, consider logging the exception

(S110)

---

676-676: Do not catch blind exception: `Exception`

(BLE001)

---

846-846: Avoid equality comparisons to `True`; use `PublicModel.is_active:` for truth checks

Replace with `PublicModel.is_active`

(E712)

---

854-854: PEP 484 prohibits implicit `Optional`

Convert to `T | None`

(RUF013)

</details>
<details>
<summary>backend/app/services/model_aggregation_service.py</summary>

130-130: Do not catch blind exception: `Exception`

(BLE001)

---

191-191: Do not catch blind exception: `Exception`

(BLE001)

</details>
<details>
<summary>shared/tests/utils/test_crypto.py</summary>

187-187: Possible hardcoded password assigned to: "token"

(S105)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)</summary>

* GitHub Check: Test Executor

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +67 to +72
import logging
logger = logging.getLogger(__name__)
logger.info(f"[DEBUG] update_bot called with bot_id={bot_id}")
logger.info(f"[DEBUG] bot_update raw: {bot_update}")
logger.info(f"[DEBUG] bot_update.agent_config: {bot_update.agent_config}")
logger.info(f"[DEBUG] bot_update.model_dump(exclude_unset=True): {bot_update.model_dump(exclude_unset=True)}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove development debug logging before merging.

This appears to be temporary debug code:

  1. Import should be at module level, not inside the function.
  2. Using logger.info with "[DEBUG]" prefix is semantically incorrect—use logger.debug for debug-level logs.
  3. Logging full bot_update payloads (including agent_config) may expose sensitive configuration data in production logs.

If logging is needed for observability, consider a minimal, structured approach:

+import logging
+
+logger = logging.getLogger(__name__)
+
 router = APIRouter()

 # ... in update_bot function:
-    import logging
-    logger = logging.getLogger(__name__)
-    logger.info(f"[DEBUG] update_bot called with bot_id={bot_id}")
-    logger.info(f"[DEBUG] bot_update raw: {bot_update}")
-    logger.info(f"[DEBUG] bot_update.agent_config: {bot_update.agent_config}")
-    logger.info(f"[DEBUG] bot_update.model_dump(exclude_unset=True): {bot_update.model_dump(exclude_unset=True)}")
-    
+    logger.debug("update_bot called with bot_id=%s", bot_id)
🤖 Prompt for AI Agents
In backend/app/api/endpoints/adapter/bots.py around lines 67 to 72, remove the
ad-hoc development debug logging: move the logging import to the module top if
not already, remove the four info statements that print the full bot_update and
agent_config, and if observability is required replace them with a single
logger.debug call that logs only non-sensitive minimal fields (e.g., bot_id and
a trimmed status or masked/hashed indicator for agent_config) or structured
metadata; ensure you use logger.debug (not logger.info with “[DEBUG]”) and do
not emit full payloads or sensitive configuration data.

Comment on lines 809 to 878
logger.info(f"[_get_bot_summary] bot.name={bot.name}, modelRef.name={bot_crd.spec.modelRef.name}, modelRef.namespace={bot_crd.spec.modelRef.namespace}")

# Get shell to extract agent_name
shell = db.query(Kind).filter(
Kind.user_id == user_id,
Kind.kind == "Shell",
Kind.name == bot_crd.spec.shellRef.name,
Kind.namespace == bot_crd.spec.shellRef.namespace,
Kind.is_active == True
).first()

agent_name = ""
if shell and shell.json:
shell_crd = Shell.model_validate(shell.json)
agent_name = shell_crd.spec.runtime

# Get model reference
model_ref_name = bot_crd.spec.modelRef.name
model_ref_namespace = bot_crd.spec.modelRef.namespace

# Try to find model in user's private models first
model = db.query(Kind).filter(
Kind.user_id == user_id,
Kind.kind == "Model",
Kind.name == model_ref_name,
Kind.namespace == model_ref_namespace,
Kind.is_active == True
).first()

logger.info(f"[_get_bot_summary] Private model found: {model is not None}")

agent_config = {}

if model:
# Private model - check if it's a custom config or predefined model
model_crd = Model.model_validate(model.json)
is_custom_config = model_crd.spec.isCustomConfig

logger.info(f"[_get_bot_summary] Private model isCustomConfig: {is_custom_config}")

if is_custom_config:
# Custom config - don't include bind_model
agent_config = {}
logger.info(f"[_get_bot_summary] Custom config (isCustomConfig=True), returning empty agent_config")
else:
# Not custom config = predefined model, return bind_model format
agent_config = {"bind_model": model_ref_name}
logger.info(f"[_get_bot_summary] Predefined model (isCustomConfig=False), returning bind_model: {agent_config}")
else:
# Try to find in public_models table
public_model = db.query(PublicModel).filter(
PublicModel.name == model_ref_name,
PublicModel.namespace == model_ref_namespace,
PublicModel.is_active == True
).first()

logger.info(f"[_get_bot_summary] Public model found: {public_model is not None}")

if public_model:
# Public model - return bind_model format
agent_config = {"bind_model": public_model.name}
logger.info(f"[_get_bot_summary] Using bind_model from public model: {agent_config}")
else:
logger.info(f"[_get_bot_summary] No model found for modelRef.name={model_ref_name}, modelRef.namespace={model_ref_namespace}")

result = {
"agent_config": agent_config,
"agent_name": agent_name
}
logger.info(f"[_get_bot_summary] Returning: {result}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Excessive INFO-level logging in _get_bot_summary.

The method contains multiple logger.info() calls that will generate significant log volume in production for every bot summary request. Consider:

  1. Using logger.debug() for detailed diagnostic logs
  2. Removing redundant log statements after development is complete
-        logger.info(f"[_get_bot_summary] bot.name={bot.name}, modelRef.name={bot_crd.spec.modelRef.name}, modelRef.namespace={bot_crd.spec.modelRef.namespace}")
+        logger.debug(f"[_get_bot_summary] bot.name={bot.name}, modelRef.name={bot_crd.spec.modelRef.name}")
         
         # ... other code ...
         
-        logger.info(f"[_get_bot_summary] Private model found: {model is not None}")
+        logger.debug(f"[_get_bot_summary] Private model found: {model is not None}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.info(f"[_get_bot_summary] bot.name={bot.name}, modelRef.name={bot_crd.spec.modelRef.name}, modelRef.namespace={bot_crd.spec.modelRef.namespace}")
# Get shell to extract agent_name
shell = db.query(Kind).filter(
Kind.user_id == user_id,
Kind.kind == "Shell",
Kind.name == bot_crd.spec.shellRef.name,
Kind.namespace == bot_crd.spec.shellRef.namespace,
Kind.is_active == True
).first()
agent_name = ""
if shell and shell.json:
shell_crd = Shell.model_validate(shell.json)
agent_name = shell_crd.spec.runtime
# Get model reference
model_ref_name = bot_crd.spec.modelRef.name
model_ref_namespace = bot_crd.spec.modelRef.namespace
# Try to find model in user's private models first
model = db.query(Kind).filter(
Kind.user_id == user_id,
Kind.kind == "Model",
Kind.name == model_ref_name,
Kind.namespace == model_ref_namespace,
Kind.is_active == True
).first()
logger.info(f"[_get_bot_summary] Private model found: {model is not None}")
agent_config = {}
if model:
# Private model - check if it's a custom config or predefined model
model_crd = Model.model_validate(model.json)
is_custom_config = model_crd.spec.isCustomConfig
logger.info(f"[_get_bot_summary] Private model isCustomConfig: {is_custom_config}")
if is_custom_config:
# Custom config - don't include bind_model
agent_config = {}
logger.info(f"[_get_bot_summary] Custom config (isCustomConfig=True), returning empty agent_config")
else:
# Not custom config = predefined model, return bind_model format
agent_config = {"bind_model": model_ref_name}
logger.info(f"[_get_bot_summary] Predefined model (isCustomConfig=False), returning bind_model: {agent_config}")
else:
# Try to find in public_models table
public_model = db.query(PublicModel).filter(
PublicModel.name == model_ref_name,
PublicModel.namespace == model_ref_namespace,
PublicModel.is_active == True
).first()
logger.info(f"[_get_bot_summary] Public model found: {public_model is not None}")
if public_model:
# Public model - return bind_model format
agent_config = {"bind_model": public_model.name}
logger.info(f"[_get_bot_summary] Using bind_model from public model: {agent_config}")
else:
logger.info(f"[_get_bot_summary] No model found for modelRef.name={model_ref_name}, modelRef.namespace={model_ref_namespace}")
result = {
"agent_config": agent_config,
"agent_name": agent_name
}
logger.info(f"[_get_bot_summary] Returning: {result}")
logger.debug(f"[_get_bot_summary] bot.name={bot.name}, modelRef.name={bot_crd.spec.modelRef.name}")
# Get shell to extract agent_name
shell = db.query(Kind).filter(
Kind.user_id == user_id,
Kind.kind == "Shell",
Kind.name == bot_crd.spec.shellRef.name,
Kind.namespace == bot_crd.spec.shellRef.namespace,
Kind.is_active == True
).first()
agent_name = ""
if shell and shell.json:
shell_crd = Shell.model_validate(shell.json)
agent_name = shell_crd.spec.runtime
# Get model reference
model_ref_name = bot_crd.spec.modelRef.name
model_ref_namespace = bot_crd.spec.modelRef.namespace
# Try to find model in user's private models first
model = db.query(Kind).filter(
Kind.user_id == user_id,
Kind.kind == "Model",
Kind.name == model_ref_name,
Kind.namespace == model_ref_namespace,
Kind.is_active == True
).first()
logger.debug(f"[_get_bot_summary] Private model found: {model is not None}")
agent_config = {}
if model:
# Private model - check if it's a custom config or predefined model
model_crd = Model.model_validate(model.json)
is_custom_config = model_crd.spec.isCustomConfig
logger.info(f"[_get_bot_summary] Private model isCustomConfig: {is_custom_config}")
if is_custom_config:
# Custom config - don't include bind_model
agent_config = {}
logger.info(f"[_get_bot_summary] Custom config (isCustomConfig=True), returning empty agent_config")
else:
# Not custom config = predefined model, return bind_model format
agent_config = {"bind_model": model_ref_name}
logger.info(f"[_get_bot_summary] Predefined model (isCustomConfig=False), returning bind_model: {agent_config}")
else:
# Try to find in public_models table
public_model = db.query(PublicModel).filter(
PublicModel.name == model_ref_name,
PublicModel.namespace == model_ref_namespace,
PublicModel.is_active == True
).first()
logger.info(f"[_get_bot_summary] Public model found: {public_model is not None}")
if public_model:
# Public model - return bind_model format
agent_config = {"bind_model": public_model.name}
logger.info(f"[_get_bot_summary] Using bind_model from public model: {agent_config}")
else:
logger.info(f"[_get_bot_summary] No model found for modelRef.name={model_ref_name}, modelRef.namespace={model_ref_namespace}")
result = {
"agent_config": agent_config,
"agent_name": agent_name
}
logger.info(f"[_get_bot_summary] Returning: {result}")
🧰 Tools
🪛 Ruff (0.14.6)

817-817: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


835-835: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


852-852: f-string without any placeholders

Remove extraneous f prefix

(F541)


862-862: Avoid equality comparisons to True; use PublicModel.is_active: for truth checks

Replace with PublicModel.is_active

(E712)

🤖 Prompt for AI Agents
In backend/app/services/adapters/team_kinds.py around lines 809 to 878, there
are many logger.info() calls generating verbose output for every bot summary
request; change diagnostic/tracing logs (those that report "Private model
found", "Private model isCustomConfig", "Custom config...", "Predefined
model...", "Public model found", "Using bind_model...", and the middle "No model
found..." messages) from logger.info() to logger.debug(), and remove or keep
only one concise logger.info() (e.g., a single final summary or error) to avoid
redundant info-level noise in production.

Comment on lines 71 to 80
try:
if 'spec' in result and 'modelConfig' in result['spec']:
model_config = result['spec']['modelConfig']
if 'env' in model_config and 'api_key' in model_config['env']:
api_key = model_config['env']['api_key']
if api_key:
result['spec']['modelConfig']['env']['api_key'] = decrypt_api_key(api_key)
except Exception as e:
logger.warning(f"Failed to decrypt API key: {str(e)}")

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Decryption failure may expose encrypted data to client.

When decryption fails, the method logs a warning but returns the result with the still-encrypted (or corrupted) API key intact. This could leak the encrypted value to the frontend.

Consider masking the API key on decryption failure:

         try:
             if 'spec' in result and 'modelConfig' in result['spec']:
                 model_config = result['spec']['modelConfig']
                 if 'env' in model_config and 'api_key' in model_config['env']:
                     api_key = model_config['env']['api_key']
                     if api_key:
                         result['spec']['modelConfig']['env']['api_key'] = decrypt_api_key(api_key)
         except Exception as e:
-            logger.warning(f"Failed to decrypt API key: {str(e)}")
+            logger.warning("Failed to decrypt API key: %s", e)
+            # Mask the key to avoid exposing encrypted data
+            if 'spec' in result and 'modelConfig' in result['spec']:
+                model_config = result['spec']['modelConfig']
+                if 'env' in model_config and 'api_key' in model_config['env']:
+                    result['spec']['modelConfig']['env']['api_key'] = "***"
🧰 Tools
🪛 Ruff (0.14.6)

78-78: Do not catch blind exception: Exception

(BLE001)


79-79: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for AI Agents
In backend/app/services/kind_impl.py around lines 71 to 80, the code logs
decryption failures but leaves the encrypted API key in the returned result
which can leak to clients; modify the exception handler so that on any
decryption error you replace/mask the API key in
result['spec']['modelConfig']['env'] (e.g., set it to None or a constant like
"****masked****") instead of leaving the original encrypted value, keep the
warning log (including error details) and ensure the structure still exists
before assigning the masked value to avoid KeyError.

Comment on lines +293 to +297
console.log('[DEBUG] fetchModels useEffect triggered', {
agentName,
baseBot_agent_name: baseBot?.agent_name,
baseBot_agent_config: baseBot?.agent_config,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statements before merging.

Multiple [DEBUG] log statements are scattered throughout the component (lines 293-297, 309, 320-325, 330, 340-345, 349, 435-441, 513, 552, 557, 562, 567). These should be removed or replaced with a conditional debug utility for production.

Example locations to clean up:

  • Lines 293-297: fetchModels useEffect debug
  • Lines 435-441: handleSave debug
  • Lines 552, 557, 562: Save request/response debug
🤖 Prompt for AI Agents
In frontend/src/features/settings/components/BotEdit.tsx around lines 293-297
(and also clean up the other reported locations: 309, 320-325, 330, 340-345,
349, 435-441, 513, 552, 557, 562, 567), remove the leftover
console.log('[DEBUG]...') statements; either delete them or replace them with a
conditional debug utility/logger that only emits when a debug flag or NODE_ENV
permits it (e.g., a simple isDebug check or use existing app logger), and ensure
no plain console.debug/console.log calls remain in these lines before merging.

Comment on lines +30 to +43
const OPENAI_MODEL_OPTIONS = [
{ value: 'gpt-4o', label: 'gpt-4o (Recommended)' },
{ value: 'gpt-4-turbo', label: 'gpt-4-turbo' },
{ value: 'gpt-4', label: 'gpt-4' },
{ value: 'gpt-3.5-turbo', label: 'gpt-3.5-turbo' },
{ value: 'custom', label: 'Custom...' },
];

const ANTHROPIC_MODEL_OPTIONS = [
{ value: 'claude-sonnet-4', label: 'claude-sonnet-4 (Recommended)' },
{ value: 'claude-opus-4', label: 'claude-opus-4' },
{ value: 'claude-haiku-4.5', label: 'claude-haiku-4.5' },
{ value: 'custom', label: 'Custom...' },
];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Anthropic Claude API current model IDs list 2025

💡 Result:

Here are the Anthropic Claude API model IDs current in 2025 (major/recent snapshots and aliases):

  • claude-opus-4-5-20251101 (alias: claude-opus-4-5). [news release]. [1]
  • claude-sonnet-4-5-20250929 (alias: claude-sonnet-4-5). [2]
  • claude-haiku-4-5-20251001 (alias: claude-haiku-4-5). [2]
  • claude-opus-4-1-20250805 (alias: claude-opus-4-1). [2]
  • claude-opus-4-20250514 (alias: claude-opus-4). [2]
  • claude-sonnet-4-20250514 (alias: claude-sonnet-4). [2][3]
  • claude-3-7-sonnet-20250219 (alias: claude-3-7-sonnet-latest). [3]
  • claude-3-5-sonnet-20241022 (alias: claude-3-5-sonnet-latest). [3]
  • claude-3-5-haiku-20241022 (alias: claude-3-5-haiku-latest). [3]

Sources: Anthropic model list / docs and recent Anthropic announcements. [1] Times/BI report on Opus 4.5 release; [2] Anthropic models overview; [3] Anthropic API models list.


Update the Anthropic model option claude-haiku-4.5 to use the correct naming convention.

The Anthropic API currently uses model IDs with hyphens (e.g., claude-opus-4, claude-sonnet-4, claude-haiku-4-5), not periods. The claude-haiku-4.5 identifier should be claude-haiku-4-5. Additionally, consider whether to use versioned aliases (claude-opus-4-5, claude-sonnet-4-5) or dated full model IDs depending on your stability/recency requirements.

Current model options (lines 39–43):

const ANTHROPIC_MODEL_OPTIONS = [
  { value: 'claude-sonnet-4', label: 'claude-sonnet-4 (Recommended)' },
  { value: 'claude-opus-4', label: 'claude-opus-4' },
  { value: 'claude-haiku-4.5', label: 'claude-haiku-4.5' },  // ← Fix: should be 'claude-haiku-4-5'
  { value: 'custom', label: 'Custom...' },
];
🤖 Prompt for AI Agents
In frontend/src/features/settings/components/ModelEdit.tsx around lines 30 to
43, the Anthropic model option uses the incorrect identifier "claude-haiku-4.5";
update the value and label to use the hyphenated model ID "claude-haiku-4-5"
(i.e., change both the value and visible label), and while editing consider
whether you want to prefer versioned aliases or dated full model IDs for
consistency across the ANTHROPIC_MODEL_OPTIONS array.

Comment on lines +312 to +320
<Button
variant="outline"
onClick={handleTestConnection}
disabled={testing || !modelId || !apiKey}
>
{testing && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
<BeakerIcon className="w-4 h-4 mr-1" />
{t('models.test_connection')}
</Button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test Connection button enabled incorrectly when "Custom" model is selected but empty.

The disabled condition checks !modelId, but when modelId === 'custom', it should check customModelId instead. Currently, the button is enabled when "Custom" is selected with an empty custom model ID.

           <Button
             variant="outline"
             onClick={handleTestConnection}
-            disabled={testing || !modelId || !apiKey}
+            disabled={testing || !apiKey || (modelId === 'custom' ? !customModelId : !modelId)}
           >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="outline"
onClick={handleTestConnection}
disabled={testing || !modelId || !apiKey}
>
{testing && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
<BeakerIcon className="w-4 h-4 mr-1" />
{t('models.test_connection')}
</Button>
<Button
variant="outline"
onClick={handleTestConnection}
disabled={testing || !apiKey || (modelId === 'custom' ? !customModelId : !modelId)}
>
{testing && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
<BeakerIcon className="w-4 h-4 mr-1" />
{t('models.test_connection')}
</Button>
🤖 Prompt for AI Agents
In frontend/src/features/settings/components/ModelEdit.tsx around lines 312 to
320, the Test Connection button's disabled check only uses !modelId so when
modelId === 'custom' and customModelId is empty the button is incorrectly
enabled; update the disabled prop to also require a non-empty customModelId when
modelId === 'custom' (e.g. disabled={testing || !modelId || (modelId ===
'custom' && !customModelId) || !apiKey}) so the button stays disabled if the
custom model ID is empty.

Comment on lines +186 to +188
{t('team.dify_no_members_hint') ||
'Dify bots handle execution independently and do not support team members.'}
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid hardcoded English fallback strings.

The fallback string after || bypasses i18n and will always display English even for non-English locales if the translation key is missing. Either ensure the translation key exists for all supported locales, or remove the fallback to surface missing translations during development.

-            {t('team.dify_no_members_hint') ||
-              'Dify bots handle execution independently and do not support team members.'}
+            {t('team.dify_no_members_hint')}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{t('team.dify_no_members_hint') ||
'Dify bots handle execution independently and do not support team members.'}
</div>
{t('team.dify_no_members_hint')}
</div>
🤖 Prompt for AI Agents
In frontend/src/features/settings/components/team-modes/BotTransfer.tsx around
lines 186 to 188, the JSX uses a hardcoded English fallback after the i18n call
(t('team.dify_no_members_hint') || '...') which forces English when a key is
missing; remove the hardcoded fallback so the component only uses
t('team.dify_no_members_hint') (or alternatively add the missing translation to
all locale files if you prefer a fallback) so missing translations surface
during development and locales remain consistent.

Comment on lines +15 to +24
console.log('[DEBUG] fetchBotsList response:', JSON.stringify(botsData, null, 2));
const items = Array.isArray(botsData.items) ? botsData.items : [];
// Log each bot's agent_config for debugging
items.forEach((bot, index) => {
console.log(`[DEBUG] Bot ${index} (${bot.name}):`, {
id: bot.id,
agent_name: bot.agent_name,
agent_config: bot.agent_config,
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug logging before merging to production.

The console.log statements with JSON.stringify will pollute browser console output in production. Additionally, logging the full API response and per-bot details (including agent_config) may inadvertently expose sensitive configuration data.

 export async function fetchBotsList(): Promise<Bot[]> {
   const params: PaginationParams = {};
   const botsData = await botApis.getBots(params);
-  console.log('[DEBUG] fetchBotsList response:', JSON.stringify(botsData, null, 2));
   const items = Array.isArray(botsData.items) ? botsData.items : [];
-  // Log each bot's agent_config for debugging
-  items.forEach((bot, index) => {
-    console.log(`[DEBUG] Bot ${index} (${bot.name}):`, {
-      id: bot.id,
-      agent_name: bot.agent_name,
-      agent_config: bot.agent_config,
-    });
-  });
   return items;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log('[DEBUG] fetchBotsList response:', JSON.stringify(botsData, null, 2));
const items = Array.isArray(botsData.items) ? botsData.items : [];
// Log each bot's agent_config for debugging
items.forEach((bot, index) => {
console.log(`[DEBUG] Bot ${index} (${bot.name}):`, {
id: bot.id,
agent_name: bot.agent_name,
agent_config: bot.agent_config,
});
});
const items = Array.isArray(botsData.items) ? botsData.items : [];
🤖 Prompt for AI Agents
In frontend/src/features/settings/services/bots.ts around lines 15 to 24, remove
the debug console.log calls that print the entire botsData and per-bot
agent_config (these leak sensitive config and clutter production consoles);
either delete those console.log lines or wrap them in an environment check
(e.g., only run in development) and if you must log per-bot info redact or omit
agent_config and any sensitive fields before logging.

Comment on lines 30 to 31
aes_key = os.environ.get('GIT_TOKEN_AES_KEY', '12345678901234567890123456789012')
aes_iv = os.environ.get('GIT_TOKEN_AES_IV', '1234567890123456')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded default encryption keys pose a security risk.

Using hardcoded fallback values for AES key and IV means data encrypted with defaults is trivially decryptable by anyone with access to the source code. If environment variables are not set, this effectively provides no security.

Consider failing loudly when keys are not configured, or at minimum log a security warning:

-        aes_key = os.environ.get('GIT_TOKEN_AES_KEY', '12345678901234567890123456789012')
-        aes_iv = os.environ.get('GIT_TOKEN_AES_IV', '1234567890123456')
+        aes_key = os.environ.get('GIT_TOKEN_AES_KEY')
+        aes_iv = os.environ.get('GIT_TOKEN_AES_IV')
+        if not aes_key or not aes_iv:
+            logger.warning(
+                "Encryption keys not configured via GIT_TOKEN_AES_KEY/GIT_TOKEN_AES_IV. "
+                "Using insecure defaults - DO NOT USE IN PRODUCTION."
+            )
+            aes_key = '12345678901234567890123456789012'
+            aes_iv = '1234567890123456'

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In shared/utils/crypto.py around lines 30-31, the AES key and IV are using
insecure hardcoded defaults from os.environ.get(..., '<default>'); remove the
fallback defaults and instead require the environment variables to be present —
validate that GIT_TOKEN_AES_KEY and GIT_TOKEN_AES_IV exist and meet expected
lengths (32 bytes for AES-256 key, 16 bytes for IV), and if they are missing or
invalid raise an explicit exception (or at startup log a security error and
exit) so the application fails loudly rather than silently using insecure
defaults.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/api/endpoints/kind/kinds.py (1)

140-159: Perfect. I now have comprehensive evidence to verify and rewrite the review comment. The findings confirm the concerns raised but with additional context about the specific pattern deviation.

Missing status code and inconsistent with REST conventions for DELETE operations

This endpoint returns HTTP 200 with a JSON body, but should follow the established pattern in the codebase: use status_code=status.HTTP_204_NO_CONTENT (as in backend/app/api/endpoints/admin.py:298 for the identical DELETE operation on kind resources). Returning JSON on a DELETE also violates REST semantics—a 204 response has no body.

Additionally, add return type hint (-> None) to align with coding guidelines requiring type hints on all Python function signatures. All endpoints in this file currently lack return type annotations.

♻️ Duplicate comments (5)
backend/app/services/kind_impl.py (1)

71-80: Decryption failure may expose encrypted data to client.

When decryption fails, the method logs a warning but returns the result with the still-encrypted (or corrupted) API key intact. This could leak the encrypted value to the frontend.

Consider masking the API key on decryption failure:

         try:
             if 'spec' in result and 'modelConfig' in result['spec']:
                 model_config = result['spec']['modelConfig']
                 if 'env' in model_config and 'api_key' in model_config['env']:
                     api_key = model_config['env']['api_key']
                     if api_key:
                         result['spec']['modelConfig']['env']['api_key'] = decrypt_api_key(api_key)
         except Exception as e:
-            logger.warning(f"Failed to decrypt API key: {str(e)}")
+            logger.warning("Failed to decrypt API key: %s", e)
+            # Mask the key to avoid exposing encrypted data
+            if 'spec' in result and 'modelConfig' in result['spec']:
+                model_config = result['spec']['modelConfig']
+                if 'env' in model_config and 'api_key' in model_config['env']:
+                    result['spec']['modelConfig']['env']['api_key'] = "***"
frontend/src/features/settings/components/team-modes/BotTransfer.tsx (1)

196-200: Hardcoded English fallback string.

This issue was previously flagged and should be addressed by removing the fallback to ensure proper i18n.

backend/app/services/adapters/team_kinds.py (1)

797-887: Excessive INFO-level logging should be reduced.

The method contains many logger.info() calls that will generate significant log noise in production. This was previously flagged and should be addressed by using logger.debug() for diagnostic logs.

frontend/src/features/settings/components/BotEdit.tsx (2)

313-317: Debug logging still present.

Multiple [DEBUG] console.log statements remain in the code. These were flagged in a previous review.


932-939: Composite key splitting vulnerability remains.

The value format using : as delimiter will produce incorrect results if a model name contains a colon character. This was flagged in a previous review.

🧹 Nitpick comments (25)
frontend/src/features/settings/components/TeamList.tsx (1)

11-12: Bot management dialog wiring looks solid; consider bots-state refresh to avoid staleness

The new Manage Bots button, botListVisible state, and Dialog with <BotList /> are wired correctly and match existing shadcn/ui patterns; imports and UI composition look good overall.

One thing to double‑check: TeamList owns a local bots state that is passed into TeamEdit, while <BotList /> is rendered without any props here. If BotList performs create/update/delete operations with its own data source, TeamList’s bots array may become stale after changes made in the Bot dialog (e.g., a newly created bot not appearing when opening TeamEdit without a full page reload).

If BotList is not already backed by a shared store (SWR/React Query/global context), consider either:

  • Exposing a callback prop such as onBotsChanged from BotList that re-runs the fetchBotsList/setBotsSorted flow in TeamList, or
  • Refactoring both TeamList and BotList to consume a shared bots source so they stay in sync automatically.

This would keep the team‑editing bot selector consistent with the standalone bot management dialog.

Also applies to: 24-25, 58-59, 352-363, 401-412

frontend/src/components/ui/alert-dialog.tsx (2)

13-26: Consider reducing overlay opacity to align with low-contrast design guidelines.

The bg-black/80 creates a relatively high-contrast overlay. Per the coding guidelines emphasizing low saturation and low contrast for reduced eye strain, consider using a lower opacity value like bg-black/60 or bg-black/50.

Based on coding guidelines: "Frontend design: Low saturation + low contrast for reduced eye strain"

   <AlertDialogPrimitive.Overlay
     className={cn(
-      'fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
+      'fixed inset-0 z-50 bg-black/60  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
       className
     )}

28-44: Consider using a more subtle shadow to align with minimal design guidelines.

The shadow-lg creates a prominent shadow effect. Per the coding guidelines emphasizing minimal shadows and subtle component differentiation, consider using shadow-sm or removing the shadow entirely since the border provides differentiation.

Based on coding guidelines: "Frontend design: Minimal shadows, subtle component differentiation (<10% background variance)"

       className={cn(
-        'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-base p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
+        'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-base p-6 shadow-sm duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
         className
       )}
backend/app/api/endpoints/kind/kinds.py (1)

140-159: Align delete_resource with project typing guidelines

Per the Python guidelines (type hints required), consider adding annotations here, e.g.:

async def delete_resource(
    namespace: str = Path(..., description="Resource namespace"),
    kinds: str = Path(..., description="Resource type. Valid options: ghosts, models, shells, bots, teams, workspaces, tasks"),
    name: str = Path(..., description="Resource name"),
    current_user: User = Depends(get_current_user),
) -> dict[str, str]:
    ...

(Adjust the return type alias as needed if you introduce a dedicated response model.)

frontend/src/features/settings/components/TeamEditDrawer.tsx (1)

18-18: Consider consolidating AgentType definition.

AgentType is defined identically in both BotEdit.tsx (line 34) and team-modes/index.ts (line 17). Consider exporting from a single source to avoid duplication and potential drift.

// In a shared types file like frontend/src/features/settings/types.ts
export type AgentType = 'ClaudeCode' | 'Agno' | 'Dify';

Then import from that shared location in both files.

backend/app/services/kind_impl.py (1)

58-61: Fix f-string and logging patterns.

Static analysis correctly identifies issues on these lines.

-                            logger.info(f"Encrypted API key for Model resource")
+                            logger.info("Encrypted API key for Model resource")
         except Exception as e:
-            logger.error(f"Failed to encrypt API key: {str(e)}")
+            logger.exception("Failed to encrypt API key")
             raise

Using logger.exception automatically includes the stack trace and exception details.

frontend/src/features/settings/components/DifyBotConfig.tsx (1)

212-218: Consider simplifying the readOnly guard pattern.

The early-return guard (if (readOnly) return;) is redundant when disabled={readOnly} is also set since the browser won't fire change events on disabled inputs.

Either approach works, but you could simplify by relying solely on the disabled attribute:

               onChange={e => {
-                if (readOnly) return;
                 setDifyApiKey(e.target.value);
               }}
               disabled={readOnly}

The current approach is more defensive and still valid if you prefer the explicit guard.

frontend/src/components/ui/transfer.tsx (1)

66-72: Good refactoring to preserve targetKeys order, minor redundancy.

The refactored rightDataSource correctly preserves the order of targetKeys for sortable functionality. However, the targetSet.has(key) check on line 70 is redundant since targetKeys is iterated directly.

   const rightDataSource = React.useMemo(() => {
-    const targetSet = new Set(targetKeys);
     const itemMap = new Map(dataSource.map(item => [item.key, item]));
     return targetKeys
-      .filter(key => targetSet.has(key) && itemMap.has(key))
+      .filter(key => itemMap.has(key))
       .map(key => itemMap.get(key)!);
   }, [dataSource, targetKeys]);
frontend/src/features/settings/components/team-modes/BotTransfer.tsx (1)

102-119: Potential edge case: leaderBotId type mismatch in filter condition.

On line 106, leaderBotId !== null doesn't account for the undefined case since the prop type is number | null. When leaderBotId is undefined, the condition is still true and the filter runs, but bots.filter(b => b.id !== undefined) will filter nothing, which is correct. However, the condition could be more explicit.

-    if (excludeLeader && leaderBotId !== null) {
+    if (excludeLeader && leaderBotId != null) {
frontend/src/features/settings/components/team-modes/index.ts (2)

13-18: Duplicate AgentType definition across files.

AgentType is also defined in BotEdit.tsx (line 34). Consider consolidating this type definition into a shared types file to avoid inconsistencies and ensure single source of truth.

Based on the relevant code snippets, both files define identical AgentType = 'ClaudeCode' | 'Agno' | 'Dify'. Extract this to a shared location like frontend/src/types/api.ts or create a dedicated frontend/src/types/agents.ts.


40-50: Type assertion could mask runtime mismatches.

The filter uses bot.agent_name as AgentType, but if bot.agent_name contains a value not in the AgentType union (e.g., a new agent type added later), the filter will incorrectly exclude valid bots without any compile-time warning.

-  return bots.filter(bot => allowedAgents.includes(bot.agent_name as AgentType));
+  return bots.filter(bot => {
+    const agentName = bot.agent_name as string;
+    return allowedAgents.some(allowed => allowed === agentName);
+  });

Alternatively, add a type guard or validation function to handle unknown agent types gracefully.

frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx (3)

25-39: Unused prop: editingTeam.

The editingTeam prop is declared in the interface but never used in the component body. Either remove it from the props or document why it's needed for future use.

 export interface SoloModeEditorProps {
   bots: Bot[];
   setBots: React.Dispatch<React.SetStateAction<Bot[]>>;
   selectedBotId: number | null;
   setSelectedBotId: React.Dispatch<React.SetStateAction<number | null>>;
-  editingTeam: Team | null;
   toast: ReturnType<typeof import('@/hooks/use-toast').useToast>['toast'];
   // ...
 }

139-145: Empty callback: handleBotCreateClose with misleading comment.

The callback body is intentionally empty with a comment explaining the rationale, but an empty function that does nothing can be confusing. Consider either:

  1. Removing the callback and passing undefined/() => {} inline
  2. Adding actual logic if needed
-  // Handle bot creation close - just close the creation mode (used for cancel)
-  const handleBotCreateClose = useCallback(() => {
-    // This is called when BotEdit's onClose is triggered
-    // We don't set isCreatingBot to false here because the useEffect will handle it
-    // when a new bot is detected. If no new bot is created (e.g., validation failed),
-    // we keep the creation mode open.
-  }, []);
+  // Empty callback - creation mode is handled by the useEffect that detects new bots
+  const handleBotCreateClose = useCallback(() => undefined, []);

276-276: Consider making minimum height configurable.

The hardcoded min-h-[500px] may not be appropriate for all screen sizes or contexts. Consider making this a prop or using a more responsive approach.

frontend/src/features/settings/components/TeamEdit.tsx (3)

94-104: Duplicate MODE_AGENT_FILTER definition.

This constant is identical to the one in index.ts. Import it from there instead of redefining it to maintain DRY principle and ensure consistency.

+import { TeamMode, getFilteredBotsForMode, AgentType, MODE_AGENT_FILTER } from './team-modes';
-
-  // Get allowed agents for current mode
-  const allowedAgentsForMode = useMemo((): AgentType[] | undefined => {
-    const MODE_AGENT_FILTER: Record<TeamMode, AgentType[] | null> = {
-      solo: null, // null means all agents are allowed
-      pipeline: ['ClaudeCode', 'Agno'],
-      route: ['Agno'],
-      coordinate: ['Agno'],
-      collaborate: ['Agno'],
-    };
-    const allowed = MODE_AGENT_FILTER[mode];
-    return allowed === null ? undefined : allowed;
-  }, [mode]);

Note: You'll need to export MODE_AGENT_FILTER from index.ts.


274-279: Dead code: selectedAgentName always returns null.

This memoized value always returns null with a comment indicating the restriction was removed. If the logic is no longer needed, remove the variable entirely rather than keeping dead code.

-  // Get currently selected agent_name (from leader or selected bot)
-  // Note: agent_name restriction has been removed - users can now select any mode
-  const selectedAgentName = useMemo(() => {
-    // No agent_name restriction - always return null
-    return null;
-  }, []);
+  // agent_name restriction has been removed - users can now select any mode
+  const selectedAgentName = null;

Then update the references to pass null directly or remove the prop if consumers don't need it.


617-863: Consider extracting inline CSS to a separate stylesheet.

The extensive use of dangerouslySetInnerHTML for CSS is not a security risk here (static content), but it's a code quality concern:

  1. CSS is not type-checked or linted
  2. Large inline styles hurt readability
  3. CSS cannot be tree-shaken or cached separately

Consider moving these styles to:

  • A CSS module (TeamEdit.module.css)
  • A global stylesheet for the Transfer component overrides
  • Using Tailwind's @apply directive in a styles file

This would improve maintainability and follow the frontend coding guidelines.

backend/app/services/adapters/team_kinds.py (3)

802-803: Avoid importing inside method.

The import of logging inside _get_bot_summary is redundant since logger is already defined at module level (line 11). Remove this local import.

     def _get_bot_summary(self, bot: Kind, db: Session, user_id: int) -> Dict[str, Any]:
         """
         Get a summary of bot information including agent_config with only necessary fields.
         This is used for team list to determine if bots have predefined models.
         """
-        import logging
-        logger = logging.getLogger(__name__)
-        
         from app.models.public_model import PublicModel

821-821: Use Pythonic truth checks instead of == True.

Per Ruff E712 and PEP 8, avoid equality comparisons to True. Use the attribute directly for truth checks.

-            Kind.is_active == True
+            Kind.is_active.is_(True)  # For SQLAlchemy, use is_() for explicit NULL-safe comparison

Or if SQLAlchemy's implicit boolean handling is acceptable:

Kind.is_active  # Without comparison

Note: In SQLAlchemy filter contexts, == True works but is_(True) is more explicit and avoids the linter warning.

Also applies to: 839-839, 868-868


748-779: Redundant database query for first bot.

The first bot's ID is obtained from bots[0]["bot_id"], then a new query fetches the same bot that was already processed in the loop above. Consider storing the first bot's shell/agent_type during the initial loop to avoid this extra query.

         bots = []
         agent_names = set()
+        first_agent_type = None
         
         for member in team_crd.spec.members:
             # Find bot in kinds table
             bot = db.query(Kind).filter(
                 # ...
             ).first()
             
             if bot:
                 bot_summary = self._get_bot_summary(bot, db, user_id)
                 bot_info = {
                     "bot_id": bot.id,
                     "bot_prompt": member.prompt or "",
                     "role": member.role or "",
                     "bot": bot_summary
                 }
                 bots.append(bot_info)
                 
                 # Collect agent_name for is_mix_team calculation
                 if bot_summary.get("agent_name"):
                     agent_names.add(bot_summary["agent_name"])
+                    # Capture first agent type during iteration
+                    if first_agent_type is None:
+                        first_agent_type = self._get_agent_type_from_bot(bot, db, user_id)
         
-        # Get agent_type from the first bot's shell (duplicate query removed)
-        agent_type = None
-        if bots:
-            # ... redundant query logic ...
+        agent_type = first_agent_type
frontend/src/features/settings/components/BotEdit.tsx (1)

34-36: Duplicate AgentType definition creates inconsistency.

AgentType is already defined in frontend/src/features/settings/utils/mcpTypeAdapter.ts (line 14) as 'ClaudeCode' | 'Agno'. This file adds 'Dify' but duplicates the type definition. This can lead to type mismatches and maintenance issues.

Consider extending the existing type in mcpTypeAdapter.ts and importing it here:

-/** Agent types supported by the system */
-export type AgentType = 'ClaudeCode' | 'Agno' | 'Dify';
+import { AgentType } from '../utils/mcpTypeAdapter';

And update mcpTypeAdapter.ts:

export type AgentType = 'ClaudeCode' | 'Agno' | 'Dify';
backend/app/services/model_aggregation_service.py (3)

130-132: Consider catching specific exceptions instead of bare Exception.

Catching Exception broadly can mask unexpected errors. Consider catching more specific exceptions like ValidationError from Pydantic.

-        except Exception as e:
-            logger.warning(f"Failed to extract model info: {e}")
+        except (ValueError, TypeError, AttributeError) as e:
+            logger.warning(f"Failed to extract model info from CRD: {e}")

Alternatively, if Pydantic ValidationError is the expected failure mode:

from pydantic import ValidationError

try:
    model_crd = Model.model_validate(model_data)
    # ...
except ValidationError as e:
    logger.warning(f"Failed to validate model CRD: {e}")
    return {"provider": None, "model_id": None, "display_name": None, "config": {}}

297-302: Hard-coded limit of 1000 may cause issues at scale.

The limit=1000 assumes there won't be more than 1000 public models. Consider either:

  1. Implementing proper pagination in the aggregation service
  2. Using a configurable constant
  3. Adding a warning log if the limit is reached
+        MAX_PUBLIC_MODELS = 1000
+        
         public_models = public_model_service.get_models(
             db=db,
             skip=0,
-            limit=1000,  # Get all public models
+            limit=MAX_PUBLIC_MODELS,
             current_user=current_user
         )
+        
+        if len(public_models) >= MAX_PUBLIC_MODELS:
+            logger.warning(f"Public models limit ({MAX_PUBLIC_MODELS}) reached, some models may be missing")

213-218: Consider restructuring try/except for clarity.

The return statement inside the try block can be moved to improve readability.

         try:
             model_crd = Model.model_validate(model_data)
-            return model_crd.spec.isCustomConfig is True
+            is_custom = model_crd.spec.isCustomConfig is True
         except Exception as e:
             logger.warning(f"Failed to check if model is custom: {e}")
             return False
+        
+        return is_custom
backend/app/services/adapters/bot_kinds.py (1)

651-651: Remove unnecessary f-string prefix.

This log message has no placeholders, so the f prefix is extraneous.

-                    logger.info(f"[DEBUG] Creating new private model for custom config")
+                    logger.info("[DEBUG] Creating new private model for custom config")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5227c0b and 28102d4.

📒 Files selected for processing (22)
  • backend/app/api/endpoints/kind/kinds.py (2 hunks)
  • backend/app/schemas/kind.py (3 hunks)
  • backend/app/services/adapters/bot_kinds.py (18 hunks)
  • backend/app/services/adapters/team_kinds.py (5 hunks)
  • backend/app/services/kind_impl.py (3 hunks)
  • backend/app/services/model_aggregation_service.py (1 hunks)
  • backend/init_data/01-default-resources.yaml (1 hunks)
  • frontend/src/components/ui/alert-dialog.tsx (1 hunks)
  • frontend/src/components/ui/transfer.tsx (8 hunks)
  • frontend/src/features/settings/components/BotEdit.tsx (31 hunks)
  • frontend/src/features/settings/components/DifyBotConfig.tsx (7 hunks)
  • frontend/src/features/settings/components/ModelList.tsx (1 hunks)
  • frontend/src/features/settings/components/TeamEdit.tsx (17 hunks)
  • frontend/src/features/settings/components/TeamEditDrawer.tsx (3 hunks)
  • frontend/src/features/settings/components/TeamList.tsx (5 hunks)
  • frontend/src/features/settings/components/team-modes/BotTransfer.tsx (1 hunks)
  • frontend/src/features/settings/components/team-modes/PipelineModeEditor.tsx (1 hunks)
  • frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx (1 hunks)
  • frontend/src/features/settings/components/team-modes/index.ts (1 hunks)
  • frontend/src/i18n/locales/en/common.json (7 hunks)
  • frontend/src/i18n/locales/zh-CN/common.json (7 hunks)
  • frontend/tailwind.config.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/features/settings/components/team-modes/PipelineModeEditor.tsx
  • frontend/src/features/settings/components/ModelList.tsx
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**

📄 CodeRabbit inference engine (AGENTS.md)

Frontend: Only use NEXT_PUBLIC_* for client-safe environment variables

Files:

  • frontend/tailwind.config.js
  • frontend/src/features/settings/components/DifyBotConfig.tsx
  • frontend/src/components/ui/transfer.tsx
  • frontend/src/features/settings/components/TeamEditDrawer.tsx
  • frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx
  • frontend/src/features/settings/components/team-modes/BotTransfer.tsx
  • frontend/src/features/settings/components/TeamList.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
  • frontend/src/features/settings/components/team-modes/index.ts
  • frontend/src/features/settings/components/TeamEdit.tsx
  • frontend/src/components/ui/alert-dialog.tsx
  • frontend/src/i18n/locales/zh-CN/common.json
  • frontend/src/i18n/locales/en/common.json
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: TypeScript strict mode must be enabled in Frontend
Use functional components with hooks in React
Use Prettier formatter in Frontend with single quotes and no semicolons
Use ESLint with Next.js configuration in Frontend
Use const over let, never use var in TypeScript
Component names must use PascalCase; file names must use kebab-case in Frontend
Use mobile-first responsive design approach in Frontend components
Frontend design: Low saturation + low contrast for reduced eye strain with generous whitespace
Frontend design: Minimal shadows, subtle component differentiation (<10% background variance)
Frontend design: Use mint blue (#14B8A6) as primary accent sparingly
Use Tailwind CSS variables for colors: --color-bg-base, --color-text-primary, --color-border, etc.
Frontend spacing: Use standard units - p-2 (8px), p-4 (16px), p-6 (24px), gap-3 (12px)
Frontend border-radius: rounded-2xl (16px) for large containers, rounded-lg (12px) for cards, rounded-md (6px) for buttons
Frontend typography: H1 text-xl font-semibold, H2 text-lg font-semibold, H3 text-base font-medium, Body text-sm
Use shadcn/ui Button component with variants: default, secondary, ghost, outline, link
Use shadcn/ui Form component with react-hook-form + zod validation

Files:

  • frontend/src/features/settings/components/DifyBotConfig.tsx
  • frontend/src/components/ui/transfer.tsx
  • frontend/src/features/settings/components/TeamEditDrawer.tsx
  • frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx
  • frontend/src/features/settings/components/team-modes/BotTransfer.tsx
  • frontend/src/features/settings/components/TeamList.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
  • frontend/src/features/settings/components/team-modes/index.ts
  • frontend/src/features/settings/components/TeamEdit.tsx
  • frontend/src/components/ui/alert-dialog.tsx
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must be PEP 8 compliant with Black formatter (line length: 88)
Use isort for import organization in Python
Python functions and variables must have descriptive names
Docstrings required for all public Python functions and classes
Extract magic numbers to named constants in Python
Python functions must not exceed 50 lines
Type hints required for all Python function signatures
Run pylint and flake8 for Python code quality checks

Files:

  • backend/app/services/kind_impl.py
  • backend/app/api/endpoints/kind/kinds.py
  • backend/app/services/model_aggregation_service.py
  • backend/app/schemas/kind.py
  • backend/app/services/adapters/bot_kinds.py
  • backend/app/services/adapters/team_kinds.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Use Kubernetes-inspired CRD design (Ghost, Model, Shell, Bot, Team, Task)
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use Tailwind CSS variables for colors: --color-bg-base, --color-text-primary, --color-border, etc.

Applied to files:

  • frontend/tailwind.config.js
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Frontend design: Minimal shadows, subtle component differentiation (<10% background variance)

Applied to files:

  • frontend/tailwind.config.js
  • frontend/src/components/ui/alert-dialog.tsx
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Frontend design: Low saturation + low contrast for reduced eye strain with generous whitespace

Applied to files:

  • frontend/tailwind.config.js
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use functional components with hooks in React

Applied to files:

  • frontend/src/components/ui/transfer.tsx
  • frontend/src/features/settings/components/BotEdit.tsx
  • frontend/src/features/settings/components/TeamEdit.tsx
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Use Kubernetes-inspired CRD design (Ghost, Model, Shell, Bot, Team, Task)

Applied to files:

  • backend/app/services/kind_impl.py
  • backend/init_data/01-default-resources.yaml
  • backend/app/services/adapters/bot_kinds.py
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use shadcn/ui Button component with variants: default, secondary, ghost, outline, link

Applied to files:

  • frontend/src/features/settings/components/BotEdit.tsx
  • frontend/src/components/ui/alert-dialog.tsx
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use mobile-first responsive design approach in Frontend components

Applied to files:

  • frontend/src/features/settings/components/TeamEdit.tsx
🧬 Code graph analysis (10)
frontend/src/components/ui/transfer.tsx (2)
frontend/src/lib/utils.ts (1)
  • cn (8-10)
frontend/src/components/ui/checkbox.tsx (1)
  • Checkbox (32-32)
frontend/src/features/settings/components/TeamEditDrawer.tsx (2)
frontend/src/features/settings/components/BotEdit.tsx (1)
  • AgentType (35-35)
frontend/src/features/settings/components/team-modes/index.ts (1)
  • AgentType (18-18)
frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx (4)
frontend/src/features/settings/components/team-modes/types.ts (1)
  • SoloModeEditorProps (27-31)
frontend/src/features/settings/components/BotEdit.tsx (1)
  • AgentType (35-35)
frontend/src/features/settings/components/team-modes/index.ts (1)
  • AgentType (18-18)
frontend/src/utils/styles.ts (2)
  • PromptBadgeVariant (39-39)
  • getPromptBadgeStyle (44-74)
frontend/src/features/settings/components/team-modes/BotTransfer.tsx (2)
frontend/src/utils/styles.ts (2)
  • getPromptBadgeStyle (44-74)
  • PromptBadgeVariant (39-39)
frontend/src/components/ui/transfer.tsx (1)
  • Transfer (36-344)
frontend/src/features/settings/components/BotEdit.tsx (4)
frontend/src/features/settings/utils/mcpTypeAdapter.ts (1)
  • AgentType (15-15)
frontend/src/types/api.ts (1)
  • Bot (29-40)
frontend/src/apis/models.ts (3)
  • UnifiedModel (73-81)
  • ModelTypeEnum (71-71)
  • modelApis (111-214)
frontend/src/features/settings/services/bots.ts (4)
  • isPredefinedModel (54-60)
  • getModelFromConfig (67-70)
  • getModelTypeFromConfig (77-86)
  • createPredefinedModelConfig (94-103)
frontend/src/features/settings/components/team-modes/index.ts (1)
backend/app/schemas/kind.py (1)
  • Bot (146-152)
backend/app/api/endpoints/kind/kinds.py (1)
backend/app/core/cache.py (1)
  • delete (90-101)
frontend/src/components/ui/alert-dialog.tsx (2)
frontend/src/lib/utils.ts (1)
  • cn (8-10)
frontend/src/components/ui/button.tsx (1)
  • buttonVariants (55-55)
backend/app/services/model_aggregation_service.py (3)
backend/app/schemas/kind.py (2)
  • Model (71-77)
  • Shell (105-111)
backend/app/services/kind_impl.py (2)
  • _format_resource (65-81)
  • _format_resource (339-387)
backend/app/services/adapters/public_model.py (1)
  • get_models (179-193)
backend/app/services/adapters/bot_kinds.py (2)
backend/app/models/kind.py (1)
  • Kind (14-30)
backend/app/schemas/kind.py (3)
  • Bot (146-152)
  • Model (71-77)
  • ModelRef (88-91)
🪛 ast-grep (0.40.0)
frontend/src/features/settings/components/TeamEdit.tsx

[warning] 618-618: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 866-866: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
frontend/src/features/settings/components/TeamEdit.tsx

[error] 619-619: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)


[error] 867-867: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 Ruff (0.14.6)
backend/app/services/kind_impl.py

58-58: f-string without any placeholders

Remove extraneous f prefix

(F541)


60-60: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


60-60: Use explicit conversion flag

Replace with conversion flag

(RUF010)


78-78: Do not catch blind exception: Exception

(BLE001)


79-79: Use explicit conversion flag

Replace with conversion flag

(RUF010)

backend/app/services/model_aggregation_service.py

130-130: Do not catch blind exception: Exception

(BLE001)


191-191: Do not catch blind exception: Exception

(BLE001)


215-215: Consider moving this statement to an else block

(TRY300)


216-216: Do not catch blind exception: Exception

(BLE001)

backend/app/services/adapters/bot_kinds.py

138-138: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


151-151: Avoid equality comparisons to True; use PublicModel.is_active: for truth checks

Replace with PublicModel.is_active

(E712)


166-166: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


177-177: Avoid equality comparisons to True; use PublicModel.is_active: for truth checks

Replace with PublicModel.is_active

(E712)


651-651: f-string without any placeholders

Remove extraneous f prefix

(F541)


734-735: try-except-pass detected, consider logging the exception

(S110)


734-734: Do not catch blind exception: Exception

(BLE001)


910-910: Avoid equality comparisons to True; use PublicModel.is_active: for truth checks

Replace with PublicModel.is_active

(E712)


918-918: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

backend/app/services/adapters/team_kinds.py

755-755: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


765-765: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


821-821: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


839-839: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)


868-868: Avoid equality comparisons to True; use PublicModel.is_active: for truth checks

Replace with PublicModel.is_active

(E712)


921-921: Avoid equality comparisons to True; use Kind.is_active: for truth checks

Replace with Kind.is_active

(E712)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Executor
🔇 Additional comments (34)
frontend/tailwind.config.js (3)

48-48: Good organizational improvement.

The comment clarifies the distinction between custom project colors and the shadcn/ui aliases added below, improving config readability.


74-94: LGTM! Logical shadcn/ui integration.

The shadcn/ui color aliases are correctly mapped to existing project CSS variables, enabling seamless component integration while maintaining the established color system. The mappings align well with the learnings about using Tailwind CSS variables.


95-95: The --color-focus-ring CSS variable is properly defined.

The variable is correctly defined in frontend/src/app/globals.css for both light and dark themes (line ~21 and ~42 respectively), set to 20 184 166 (teal color matching --color-primary). The Tailwind config ring mapping to this variable is correct and will function as intended for focus states.

frontend/src/components/ui/alert-dialog.tsx (1)

1-115: Well-structured shadcn/ui AlertDialog component implementation.

The component follows most coding guidelines effectively:

  • ✓ Uses functional components with forwardRef
  • ✓ Single quotes, no semicolons
  • ✓ Mobile-first responsive design
  • ✓ Proper spacing units and border-radius
  • ✓ Correct typography hierarchy
  • ✓ Uses Tailwind CSS variables and buttonVariants
  • ✓ PascalCase components, kebab-case filename

The two optional refactors suggested above (overlay opacity and shadow) are minor design consistency improvements.

frontend/src/i18n/locales/zh-CN/common.json (4)

314-315: Settings section updated with new Models tab.

Lines 314–315 add "team" and "models" keys to the settings navigation, aligning with the PR objective to add a Models tab between Team and Integrations.


118-122: Bot and team sections updated to support protocol selection and mode switching.

Lines 118–122 add protocol-related keys (protocol, protocol_select, protocol_hint, select_executor_first, model_select) to the bot section, supporting runtime model resolution and provider selection (OpenAI vs. Anthropic). Lines 240–246 add keys for team mode switching UI (mode_change_confirm_title, mode_change_confirm_message, solo_max_one_bot, select_bot, etc.).

Also applies to: 240-246


531-539: Task submission UI strings added for model selection override.

The new task_submit section covers UI labels for model selection (select_model, search_model), force override (force_override_model, override_short), and handling mixed executor types (mixed_team_warning). These align with task-level model override functionality.


477-530: English and Chinese translations are fully aligned; all component i18n references are correctly mapped.

Verification confirms:

  1. Locale parity verified: frontend/src/i18n/locales/en/common.json and frontend/src/i18n/locales/zh-CN/common.json both contain identical models and task_submit key structures.

  2. Component key alignment verified: All three Model-related components correctly reference the defined translations:

    • ModelEdit.tsx uses 34 models.* keys (form fields, hints, success/error messages)
    • ModelList.tsx uses 15 models.* keys (CRUD operations, titles, descriptions, errors)
    • ModelSelector.tsx uses 6 task_submit.* keys (model selection UI)
  3. No gaps detected: Every t() call in the components matches an existing translation key. No missing or unused translation entries found.

  4. Both locales complete: Chinese and English translations are identical in structure and comprehensive in coverage.

backend/init_data/01-default-resources.yaml (1)

76-85: LGTM! Well-structured Shell CRD resource.

The new claude-shell resource follows the Kubernetes-inspired CRD design pattern consistently with other resources, properly decoupling shell configuration from model references.

backend/app/schemas/kind.py (3)

17-17: LGTM! Good addition of displayName for human-readable resource names.

The displayName field in ObjectMeta provides a clean way to display user-friendly names while preserving the machine-readable name field for internal references.


62-63: LGTM! Model configuration flexibility.

The isCustomConfig and protocol fields properly support distinguishing between predefined and custom model configurations, with appropriate typing.


138-138: LGTM! Making modelRef optional aligns with the decoupled model design.

This change supports the new model resolution flow where Bots can operate without a direct model reference, relying on the shell-based or unified model discovery approach.

frontend/src/features/settings/components/TeamEditDrawer.tsx (1)

38-39: LGTM! Clean prop forwarding for agent type filtering.

The allowedAgents prop is properly typed and documented, and correctly passed through to the BotEdit component.

Also applies to: 359-359

frontend/src/features/settings/components/DifyBotConfig.tsx (2)

25-26: LGTM! Clean addition of readOnly mode.

The readOnly prop with proper default value follows React patterns and is well-documented with JSDoc.

Also applies to: 48-48


250-253: LGTM! Validation button properly respects readOnly state.

The disabled condition correctly combines all relevant states: isValidating || !difyApiKey || !difyBaseUrl || readOnly.

frontend/src/components/ui/transfer.tsx (3)

120-172: LGTM! Well-implemented drag-and-drop handlers.

The drag handlers properly:

  • Set appropriate effectAllowed and dropEffect
  • Handle edge cases (same item drop, invalid indices)
  • Clean up drag state on completion/cancellation
  • Use immutable array operations for reordering

245-281: LGTM! Clean drag-enabled item rendering.

The conditional drag behavior based on canDrag is well-implemented:

  • Drag attributes only applied when appropriate
  • Visual feedback via CSS classes for dragging/drag-over states
  • GripVertical handle provides clear affordance
  • Click-to-select preserved alongside drag functionality

338-339: LGTM! Correct wiring of isRightList for right panel.

The isRightList: true parameter is correctly passed to enable drag-and-drop only on the right side.

frontend/src/features/settings/components/team-modes/BotTransfer.tsx (4)

1-17: LGTM: Clean imports and proper component setup.

The component follows React best practices with proper imports, use of useMemo for memoization, and appropriate typing. The imports are well-organized and use the correct path aliases.


18-38: Well-defined props interface with good documentation.

The BotTransferProps interface is comprehensive with JSDoc comments for the optional props. Consider adding JSDoc comments to the remaining props for consistency.


122-150: Transfer change handler logic is correct.

The onTransferChange handler properly handles both directions, auto-sets leader when appropriate, and maintains state consistency when the leader is removed. Good use of Set for deduplication.


261-264: Consider using CSS variables consistently.

The listStyle uses rgb(var(--color-bg-surface)) format, which is consistent with the coding guidelines for Tailwind CSS variables. Good adherence to the design system.

frontend/src/features/settings/components/team-modes/SoloModeEditor.tsx (1)

152-171: Effect correctly handles new bot detection.

The effect properly tracks previous bot IDs using a ref and auto-selects newly created bots. The logic is sound, and the dependency array is correct. Good pattern for detecting external state changes.

frontend/src/features/settings/components/TeamEdit.tsx (1)

1033-1047: Mode change confirmation dialog is well implemented.

The dialog properly uses shadcn/ui components, handles both confirm and cancel actions, and integrates with the state management. Good UX pattern for preventing accidental data loss.

backend/app/services/adapters/team_kinds.py (1)

1071-1077: Good guard for optional modelRef.

The early return when modelRef is missing prevents null pointer errors in the subsequent query. This is a proper defensive coding pattern.

frontend/src/features/settings/components/BotEdit.tsx (3)

877-887: LGTM on protocol filtering logic.

The agent-specific protocol filtering correctly limits ClaudeCode to Claude protocol and Agno to OpenAI protocol, with fallback to showing all options for unknown/unselected agents.


353-373: LGTM on model type-aware restoration.

The model restoration logic correctly handles type disambiguation when restoring saved predefined models. It matches by both name and type when type is specified, falling back to name-only matching when type is absent.


636-653: LGTM on read-only mode implementation.

The read-only mode is consistently implemented across the component, with proper button state handling and visual feedback. The edit button correctly triggers onEditClick callback and the cancel button only appears when embedded && onCancelEdit.

backend/app/services/model_aggregation_service.py (1)

416-453: LGTM on resolve_model implementation.

The resolve_model method correctly implements type-aware resolution with sensible fallback behavior (user models first, then public models). The error handling for invalid model types is appropriate.

backend/app/services/adapters/bot_kinds.py (3)

61-76: LGTM on predefined model detection.

The logic correctly identifies predefined model configurations by checking that bind_model is present and no other keys exist besides the optional bind_model_type. This mirrors the frontend implementation in frontend/src/features/settings/services/bots.ts.


901-914: LGTM on batch public model fallback.

The implementation correctly handles missing models by batch-fetching from the public_models table, maintaining the N+1 query optimization while supporting the hybrid model architecture.


959-1012: LGTM on agent_config determination logic.

The logic correctly handles all model configuration scenarios:

  1. Override agent_config (for predefined models)
  2. PublicModel instances (returns bind_model_type: "public")
  3. Shared/predefined models (returns bind_model_type: "user")
  4. Custom config models (returns full modelConfig with protocol)
  5. Dedicated models without isCustomConfig (backward compatibility)

The bind_model_type field is essential for the frontend to correctly identify model sources and avoid naming conflicts. Based on learnings, this aligns with the Kubernetes-inspired CRD design pattern.

frontend/src/i18n/locales/en/common.json (2)

476-529: LGTM on Model Management i18n translations.

The new models section provides comprehensive translations for the Model Management module:

  • Clear, user-friendly labels and hints
  • Complete error message coverage
  • Consistent naming conventions with the rest of the file

248-260: LGTM on team mode translations.

The new solo mode translations are well-written and clearly explain the single-bot independent handling pattern. The description aligns with the collaboration mode semantics.

Comment on lines +128 to +129
import logging
logger = logging.getLogger(__name__)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Move logger initialization to module level.

Importing and initializing the logger inside methods is inefficient and unconventional. The logger should be initialized once at the module level.

Add at the top of the file (after imports):

import logging

logger = logging.getLogger(__name__)

Then remove the inline imports at lines 128-129, 473-474, 805-806, and 930-931.

🤖 Prompt for AI Agents
In backend/app/services/adapters/bot_kinds.py around lines 128-129 (also
affecting inline logger inits at 473-474, 805-806, and 930-931), the code
imports and initializes the logging.Logger inside functions; move the logging
setup to module scope by adding a single import logging and logger =
logging.getLogger(__name__) at the top of the file (after other imports) and
remove the inline import logging / logger = ... statements at the listed line
ranges so all functions use the module-level logger.

"team_model_desc": {
"solo": "A single bot handles the task independently, suitable for simple scenarios or single-responsibility workflows.",
"pipeline": "Agents are chained one after another, where each agent refines or transforms the result in turn.",
"route": "A central agent acts as a message router, directing communication between agents without them talking directiy.",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in route mode description.

"directiy" should be "directly".

-    "route": "A central agent acts as a message router, directing communication between agents without them talking directiy.",
+    "route": "A central agent acts as a message router, directing communication between agents without them talking directly.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"route": "A central agent acts as a message router, directing communication between agents without them talking directiy.",
"route": "A central agent acts as a message router, directing communication between agents without them talking directly.",
🤖 Prompt for AI Agents
In frontend/src/i18n/locales/en/common.json around line 257, the string value
for "route" contains a typo: "directiy" should be "directly"; update the JSON
entry to replace "directiy" with "directly" and ensure the surrounding JSON
punctuation and escaping remain valid.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (4)
backend/app/services/kind_impl.py (2)

137-137: Verify removal of Model reference validation - downstream code may have unsafe access.

The Model existence check was removed from _validate_references in BotKindService. As noted in a previous review, downstream code in task_kinds.py and executor_kinds.py may directly access bot_crd.spec.modelRef.name without None checks.

Please verify that all downstream code handles modelRef=None safely. Run this script to check for unsafe access patterns:

#!/bin/bash
# Search for direct modelRef attribute access without None checks
rg -n -C3 'modelRef\.(name|namespace)' backend/app/services/adapters/

71-80: Decryption failure may expose encrypted data to client.

When decryption fails, the method logs a warning but returns the result with the still-encrypted (or corrupted) API key intact. This could leak the encrypted value to the frontend.

Apply this diff to mask the API key on decryption failure:

         try:
             if 'spec' in result and 'modelConfig' in result['spec']:
                 model_config = result['spec']['modelConfig']
                 if 'env' in model_config and 'api_key' in model_config['env']:
                     api_key = model_config['env']['api_key']
                     if api_key:
                         result['spec']['modelConfig']['env']['api_key'] = decrypt_api_key(api_key)
         except (ValueError, KeyError) as e:
             logger.warning("Failed to decrypt API key: %r", e)
+            # Mask the key to avoid exposing encrypted data
+            if 'spec' in result and 'modelConfig' in result['spec']:
+                model_config = result['spec']['modelConfig']
+                if 'env' in model_config and 'api_key' in model_config['env']:
+                    result['spec']['modelConfig']['env']['api_key'] = "***"
backend/app/services/adapters/team_kinds.py (1)

796-886: Excessive INFO-level logging in _get_bot_summary.

The method contains 10+ logger.info() calls with DEBUG-level prefixes that will generate significant log volume in production. These should be logger.debug().

Apply this diff to use appropriate log levels:

-        logger.info(f"[_get_bot_summary] bot.name={bot.name}, modelRef.name={model_ref_name}, modelRef.namespace={model_ref_namespace}")
+        logger.debug(f"[_get_bot_summary] bot.name={bot.name}, modelRef.name={model_ref_name}")
         
         # ... (apply similar changes to all other logger.info calls in this method)
         
-        logger.info(f"[_get_bot_summary] Private model found: {model is not None}")
+        logger.debug(f"[_get_bot_summary] Private model found: {model is not None}")
backend/app/services/adapters/bot_kinds.py (1)

128-129: Move logger initialization to module level.

The logger is imported and initialized inside methods at multiple locations (lines 128-129, 473-474, 805-806, 930-931). This is inefficient and unconventional.

Add the logger at module level after imports (around line 24):

+import logging
+
+logger = logging.getLogger(__name__)
+

 class BotKindsService(BaseService[Kind, BotCreate, BotUpdate]):

Then remove the inline imports at lines 128-129, 473-474, 805-806, and 930-931.

🧹 Nitpick comments (9)
backend/app/services/adapters/team_kinds.py (1)

748-778: Redundant database query for first_bot.

The code queries first_bot by ID at lines 751-756, but this bot was already fetched in the loop at lines 722-728 and stored in bots[0]. This results in an unnecessary database round trip.

Refactor to reuse the already-fetched bot:

         # Get agent_type from the first bot's shell
         agent_type = None
-        if bots:
-            first_bot_id = bots[0]["bot_id"]
-            first_bot = db.query(Kind).filter(
-                Kind.id == first_bot_id,
-                Kind.user_id == user_id,
-                Kind.kind == "Bot",
-                Kind.is_active.is_(True)
-            ).first()
-            
-            if first_bot:
+        if team_crd.spec.members:
+            first_member = team_crd.spec.members[0]
+            first_bot = db.query(Kind).filter(
+                Kind.user_id == user_id,
+                Kind.kind == "Bot",
+                Kind.name == first_member.botRef.name,
+                Kind.namespace == first_member.botRef.namespace,
+                Kind.is_active.is_(True)
+            ).first()
+            if first_bot:
                 bot_crd = Bot.model_validate(first_bot.json)

Alternatively, extract agent_type from the first bot_summary's agent_name since it's already computed.

backend/app/services/model_aggregation_service.py (3)

268-275: Accessing private method _format_resource from external service.

Line 270 calls kind_service._format_resource("Model", resource), which is a private method (indicated by the underscore prefix). This creates tight coupling and may break if the internal implementation changes.

Consider either:

  1. Make _format_resource a public method if it's intended for external use
  2. Add a public method in kind_service that returns formatted resource data
  3. Extract the model info directly from resource.json here
# Alternative approach - extract directly from resource
model_data = resource.json  # resource.json contains the CRD data

297-303: Hardcoded limit=1000 may miss models or cause performance issues.

The hardcoded limit=1000 could miss models if there are more than 1000 public models, or cause memory pressure if there are many models.

Consider:

  1. Using pagination and aggregating results
  2. Making the limit configurable
  3. Adding a warning if the limit is reached
MAX_PUBLIC_MODELS = 1000  # Extract to constant

public_models = public_model_service.get_models(
    db=db,
    skip=0,
    limit=MAX_PUBLIC_MODELS,
    current_user=current_user
)

if len(public_models) >= MAX_PUBLIC_MODELS:
    logger.warning("Public models limit reached, some models may be missing")

391-413: Inefficient lookup for public model by name.

The get_model_by_name_and_type method fetches all public models (up to 1000) to find one by name. This is inefficient when only a single model is needed.

Add a dedicated method in public_model_service to fetch by name, or query directly:

elif model_type == ModelType.PUBLIC:
    from app.models.public_model import PublicModel
    
    public_model = db.query(PublicModel).filter(
        PublicModel.name == name,
        PublicModel.is_active.is_(True)
    ).first()
    
    if public_model:
        config = public_model.json.get("spec", {}).get("modelConfig", {}) if public_model.json else {}
        env = config.get("env", {}) if isinstance(config, dict) else {}
        
        return UnifiedModel(
            name=public_model.name,
            model_type=ModelType.PUBLIC,
            display_name=None,
            provider=env.get("model") if isinstance(env, dict) else None,
            model_id=env.get("model_id") if isinstance(env, dict) else None,
            config=config,
            is_active=public_model.is_active
        ).to_full_dict()
backend/app/services/adapters/bot_kinds.py (1)

467-737: Method update_with_user exceeds recommended length (~270 lines).

This method handles many concerns: name validation, model switching (predefined vs custom), protocol extraction, shell updates, ghost updates, and timestamp management. This exceeds the 50-line guideline significantly and makes the code harder to maintain.

Consider extracting into smaller focused methods:

def update_with_user(self, db: Session, *, bot_id: int, obj_in: BotUpdate, user_id: int) -> Dict[str, Any]:
    bot = self._get_bot_for_update(db, bot_id, user_id)
    update_data = obj_in.model_dump(exclude_unset=True)
    
    self._handle_name_update(db, bot, update_data, user_id)
    
    ghost, shell, model = self._get_bot_components(db, bot, user_id)
    
    self._handle_agent_name_update(db, shell, update_data)
    model, return_agent_config = self._handle_agent_config_update(
        db, bot, model, update_data, user_id
    )
    self._handle_ghost_updates(ghost, update_data, db, user_id)
    
    self._update_timestamps(bot, ghost, shell, model)
    
    db.commit()
    # ... refresh and return

As per coding guidelines, Python functions should not exceed 50 lines.

frontend/src/features/settings/components/TeamEdit.module.css (1)

5-386: Consider refactoring for maintainability.

This CSS module contains extensive responsive overrides (382 lines) with significant duplication across breakpoints and heavy !important usage. While the comprehensive mobile/tablet/desktop coverage is good, consider:

  • Extracting common patterns into reusable rules
  • Documenting why specific !important declarations are needed for Ant Design overrides
  • Consolidating duplicate Transfer component rules that appear across multiple breakpoints

For example, Transfer list overflow prevention appears in lines 29-37, 180-193, 255-265, 282-288, and 308-317. These could potentially be consolidated with breakpoint-specific variations only where needed.

frontend/src/features/settings/components/TeamEdit.tsx (3)

333-421: Consider adding Zod validation for the form.

The save handler validates inputs using inline checks, but the coding guidelines specify using react-hook-form + zod validation for forms. While the current validation works, adopting the prescribed approach would provide:

  • Type-safe schema validation
  • Better error handling and user feedback
  • Consistency with the project's form handling patterns

As per coding guidelines: "Use shadcn/ui Form component with react-hook-form + zod validation"

Example structure:

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const teamSchema = z.object({
  name: z.string().min(1, 'Team name is required'),
  mode: z.enum(['solo', 'pipeline', 'route', 'coordinate', 'collaborate']),
  leaderBotId: z.number().nullable().refine(val => val !== null, {
    message: 'Bot is required'
  }),
  // ... other fields
})

const form = useForm({
  resolver: zodResolver(teamSchema),
  defaultValues: { ... }
})

439-440: Simplify the long className string.

The className combines multiple utilities with template literal and CSS module reference, reducing readability.

Consider using the clsx utility (already imported in dependencies) to improve clarity:

+import { clsx } from 'clsx'

-    <div
-      className={`flex flex-col flex-1 min-h-0 items-stretch bg-surface rounded-lg pt-0 pb-4 relative w-full max-w-none px-0 md:px-4 overflow-hidden ${styles.teamEditContainer}`}
-    >
+    <div
+      className={clsx(
+        'flex flex-col flex-1 min-h-0 items-stretch bg-surface rounded-lg',
+        'pt-0 pb-4 relative w-full max-w-none px-0 md:px-4 overflow-hidden',
+        styles.teamEditContainer
+      )}
+    >

277-282: Remove confusing comment about agent_name restriction.

The comment states "agent_name restriction has been removed" and "No agent_name restriction - always return null", but the code simply returns null without any conditional logic. This creates confusion about the purpose of this memoized value.

If selectedAgentName is no longer needed, consider removing it entirely along with its usages (line 609). If it's needed for backward compatibility or future use, clarify the comment:

-  // Get currently selected agent_name (from leader or selected bot)
-  // Note: agent_name restriction has been removed - users can now select any mode
   const selectedAgentName = useMemo(() => {
-    // No agent_name restriction - always return null
+    // Agent name filtering is no longer enforced for mode selection
     return null;
-  }, []);
+  }, [])

Or remove if unused:

-  const selectedAgentName = useMemo(() => {
-    return null;
-  }, []);
-
   // ... later in code
   <LeaderModeEditor
     // ...
-    selectedAgentName={selectedAgentName}
   />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28102d4 and 2664be3.

📒 Files selected for processing (6)
  • backend/app/services/adapters/bot_kinds.py (18 hunks)
  • backend/app/services/adapters/team_kinds.py (5 hunks)
  • backend/app/services/kind_impl.py (3 hunks)
  • backend/app/services/model_aggregation_service.py (1 hunks)
  • frontend/src/features/settings/components/TeamEdit.module.css (1 hunks)
  • frontend/src/features/settings/components/TeamEdit.tsx (15 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must be PEP 8 compliant with Black formatter (line length: 88)
Use isort for import organization in Python
Python functions and variables must have descriptive names
Docstrings required for all public Python functions and classes
Extract magic numbers to named constants in Python
Python functions must not exceed 50 lines
Type hints required for all Python function signatures
Run pylint and flake8 for Python code quality checks

Files:

  • backend/app/services/kind_impl.py
  • backend/app/services/adapters/team_kinds.py
  • backend/app/services/model_aggregation_service.py
  • backend/app/services/adapters/bot_kinds.py
frontend/**

📄 CodeRabbit inference engine (AGENTS.md)

Frontend: Only use NEXT_PUBLIC_* for client-safe environment variables

Files:

  • frontend/src/features/settings/components/TeamEdit.module.css
  • frontend/src/features/settings/components/TeamEdit.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx}: TypeScript strict mode must be enabled in Frontend
Use functional components with hooks in React
Use Prettier formatter in Frontend with single quotes and no semicolons
Use ESLint with Next.js configuration in Frontend
Use const over let, never use var in TypeScript
Component names must use PascalCase; file names must use kebab-case in Frontend
Use mobile-first responsive design approach in Frontend components
Frontend design: Low saturation + low contrast for reduced eye strain with generous whitespace
Frontend design: Minimal shadows, subtle component differentiation (<10% background variance)
Frontend design: Use mint blue (#14B8A6) as primary accent sparingly
Use Tailwind CSS variables for colors: --color-bg-base, --color-text-primary, --color-border, etc.
Frontend spacing: Use standard units - p-2 (8px), p-4 (16px), p-6 (24px), gap-3 (12px)
Frontend border-radius: rounded-2xl (16px) for large containers, rounded-lg (12px) for cards, rounded-md (6px) for buttons
Frontend typography: H1 text-xl font-semibold, H2 text-lg font-semibold, H3 text-base font-medium, Body text-sm
Use shadcn/ui Button component with variants: default, secondary, ghost, outline, link
Use shadcn/ui Form component with react-hook-form + zod validation

Files:

  • frontend/src/features/settings/components/TeamEdit.tsx
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Use Kubernetes-inspired CRD design (Ghost, Model, Shell, Bot, Team, Task)
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Use Kubernetes-inspired CRD design (Ghost, Model, Shell, Bot, Team, Task)

Applied to files:

  • backend/app/services/kind_impl.py
  • backend/app/services/adapters/bot_kinds.py
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use mobile-first responsive design approach in Frontend components

Applied to files:

  • frontend/src/features/settings/components/TeamEdit.module.css
  • frontend/src/features/settings/components/TeamEdit.tsx
📚 Learning: 2025-11-26T07:34:32.591Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-26T07:34:32.591Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use functional components with hooks in React

Applied to files:

  • frontend/src/features/settings/components/TeamEdit.tsx
🧬 Code graph analysis (3)
backend/app/services/adapters/team_kinds.py (3)
backend/app/schemas/kind.py (4)
  • Team (187-193)
  • Bot (146-152)
  • Shell (105-111)
  • Model (71-77)
frontend/src/types/api.ts (3)
  • Team (86-102)
  • Bot (29-40)
  • Shell (75-84)
backend/app/models/kind.py (1)
  • Kind (14-30)
backend/app/services/model_aggregation_service.py (3)
backend/app/schemas/kind.py (2)
  • Model (71-77)
  • Shell (105-111)
backend/app/services/kind_impl.py (2)
  • _format_resource (65-81)
  • _format_resource (339-387)
backend/app/services/adapters/public_model.py (1)
  • get_models (179-193)
backend/app/services/adapters/bot_kinds.py (2)
backend/app/models/kind.py (1)
  • Kind (14-30)
backend/app/schemas/kind.py (3)
  • Bot (146-152)
  • Model (71-77)
  • ModelRef (88-91)
🪛 Ruff (0.14.6)
backend/app/services/kind_impl.py

59-59: Redundant exception object included in logging.exception call

(TRY401)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Executor
🔇 Additional comments (13)
backend/app/services/kind_impl.py (1)

42-63: LGTM for API key encryption logic.

The _extract_resource_data method correctly encrypts API keys before storage, with proper checks for already-encrypted values and the masked placeholder "***". The exception handling re-raises ValueError appropriately (after fixing the duplicate raise).

backend/app/services/adapters/team_kinds.py (2)

912-921: LGTM for optional modelRef handling.

The _convert_bot_to_dict method now correctly handles the optional modelRef by checking if bot_crd.spec.modelRef before querying for the model.


1070-1076: LGTM for get_team_input_parameters modelRef handling.

The method correctly handles optional modelRef by returning early with has_parameters: False when no model reference exists.

backend/app/services/model_aggregation_service.py (2)

29-93: LGTM for ModelType enum and UnifiedModel class.

The ModelType enum and UnifiedModel class are well-designed with clear documentation. The to_dict and to_full_dict methods provide clean API response formatting with the important type field for distinguishing model sources.


134-167: LGTM for agent compatibility check.

The _is_model_compatible_with_agent method provides a clean fallback chain: shell's supportModel → agent provider mapping → allow all. This is well-structured and documented.

backend/app/services/adapters/bot_kinds.py (5)

731-735: LGTM for exception handling improvement.

The try-except block now properly logs the exception with logger.debug instead of silently passing. This addresses the previous review comment.


61-104: LGTM for predefined model helper methods.

The new helper methods (_is_predefined_model, _get_model_name_from_config, _get_model_type_from_config, _get_protocol_from_config) are well-designed with clear single responsibilities and proper docstrings.


105-194: LGTM for model lookup methods with type support.

The _get_model_by_name_and_type and _get_model_by_name methods provide clean fallback logic from user models to public models, with proper type hints and docstrings.

Note: The DEBUG-level logging (lines 142, 155, 170, 180, 184) should remain as logger.debug once the module-level logger is added.


918-1012: LGTM for _convert_to_bot_dict model type differentiation.

The method correctly handles multiple scenarios:

  • Override agent_config for explicit predefined models
  • Public models → returns bind_model_type: "public"
  • Shared/predefined user models → returns bind_model_type: "user"
  • Dedicated custom models → returns full config with protocol

The bind_model_type field properly identifies the model source to avoid naming conflicts.


901-914: LGTM for batch public model lookup.

The _get_bot_components_batch method now efficiently fetches missing models from the public_models table using a single query with OR filters, avoiding N+1 queries.

frontend/src/features/settings/components/TeamEdit.tsx (3)

87-276: Well-implemented mode change confirmation flow.

The mode change confirmation logic is well structured with proper state management, clear separation between checking conditions (needsModeChangeConfirmation), executing changes (executeModeChange), and handling user actions (handleModeChange, handleConfirmModeChange, handleCancelModeChange). The confirmation properly resets team members and prompts when the mode changes.


502-503: Verify mobile layout for 5-column mode selector.

The RadioGroup uses grid-cols-5 to display five mode options in a single row. On mobile screens (especially devices <375px wide), this may result in cramped buttons with truncated text or poor touch targets.

Consider testing on small mobile devices or using a responsive grid pattern:

-                  className="w-full grid grid-cols-5 gap-2"
+                  className="w-full grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2"

This would display 2 columns on mobile, 3 on small tablets, and 5 on larger screens, ensuring better usability across all viewport sizes.


561-618: Well-structured mode-specific editor rendering.

The conditional rendering of mode-specific editors (SoloModeEditor, PipelineModeEditor, LeaderModeEditor) is clean and maintainable. Each editor receives the appropriate props and is wrapped in consistent styling. The separation of concerns between different modes improves code organization.

Comment on lines +801 to +804
import logging
logger = logging.getLogger(__name__)

from app.models.public_model import PublicModel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Move logger initialization to module level.

Importing and initializing the logger inside methods is inefficient. The logger is already initialized at module level (line 11), so these inline imports are redundant.

Remove the inline imports at lines 801-802 and 804:

     def _get_bot_summary(self, bot: Kind, db: Session, user_id: int) -> Dict[str, Any]:
         """
         Get a summary of bot information including agent_config with only necessary fields.
         This is used for team list to determine if bots have predefined models.
         """
-        import logging
-        logger = logging.getLogger(__name__)
-        
-        from app.models.public_model import PublicModel
+        from app.models.public_model import PublicModel

Note: PublicModel import should be moved to module level (line 12 area) alongside other imports, or kept here if needed to avoid circular imports.

🤖 Prompt for AI Agents
In backend/app/services/adapters/team_kinds.py around lines 801 to 804, remove
the inline logger import/initialization and the inline PublicModel import and
instead use the module-level logger already defined at the top of the file (line
~11); also move the PublicModel import to module level near the other imports
(around line ~12) unless doing so creates a circular import—in that case keep
PublicModel import at this location but remove the redundant logger import;
ensure no other references rely on the removed local logger variable.

Comment on lines +58 to +62
except ValueError as e:
logger.exception("Failed to encrypt API key: %r", e)
raise
raise

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Duplicate raise statement causes syntax error.

Line 61 contains a duplicate raise statement that will cause a syntax error when executed. This appears to be a copy-paste mistake.

Apply this diff to fix the duplicate raise:

         except ValueError as e:
             logger.exception("Failed to encrypt API key: %r", e)
             raise
-            raise
🧰 Tools
🪛 Ruff (0.14.6)

59-59: Redundant exception object included in logging.exception call

(TRY401)

🤖 Prompt for AI Agents
In backend/app/services/kind_impl.py around lines 58 to 62, there's a duplicate
`raise` after catching ValueError which causes a syntax error; remove the extra
`raise` so only a single `raise` remains (preserve the logger.exception call and
re-raise the original exception once).

Comment on lines +65 to +68
.teamEditContainer :global(body),
.teamEditContainer :global(html) {
overflow-x: hidden !important;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Invalid selector scope for body and html elements.

These selectors attempt to apply overflow-x: hidden to body and html elements from within the .teamEditContainer scope. However, since body and html are ancestors of .teamEditContainer, this scoped selector won't match these elements correctly. The :global() wrapper doesn't change the ancestor relationship.

If you need to prevent horizontal overflow globally, consider one of these approaches:

  1. Apply the style directly in a global stylesheet (not scoped to .teamEditContainer)
  2. Use a layout wrapper component that applies these styles at the correct level
  3. Remove these rules if the other overflow prevention rules (lines 120-123, 237-240, etc.) are sufficient
-  /* Prevent horizontal scroll on mobile */
-  .teamEditContainer :global(body),
-  .teamEditContainer :global(html) {
-    overflow-x: hidden !important;
-  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/src/features/settings/components/TeamEdit.module.css around lines
65-68, the selectors ".teamEditContainer :global(body)" and ".teamEditContainer
:global(html)" are invalid because body and html are ancestors and cannot be
targeted from a child scope; move the overflow-x: hidden rule to a global
stylesheet or a top-level layout wrapper (or remove these rules if other global
overflow rules suffice) so the styles apply at the correct level rather than
inside the module scope.

@qdaxb qdaxb merged commit 4b88fd5 into main Nov 28, 2025
5 of 7 checks passed
@qdaxb qdaxb deleted the wegent/model-management-redesign branch November 28, 2025 16:56
qdaxb added a commit that referenced this pull request Nov 28, 2025
- Update AGENTS.md with new Model Management section
  - Document model types (public/user) and resolution order
  - Add key API endpoints for model operations
  - Document bind_model configuration pattern
  - Update version to 1.0.8

- Update configuring-models.md (en/zh)
  - Add model types documentation (public/user)
  - Update configuration steps for new Model Management UI
  - Add connection test feature documentation
  - Add per-task model selection documentation
  - Document model resolution priority

- Update creating-bots.md (en/zh)
  - Document bind_model as recommended model binding method
  - Make modelRef optional in spec section
  - Add bind_model_type for model type hints

- Update managing-tasks.md (en/zh)
  - Add model_id and force_override_bot_model fields
  - Document per-task model selection feature

These documentation updates reflect the changes from PR #194 which
added the model management module with unified model view,
connection testing, and per-task model selection.
qdaxb added a commit that referenced this pull request Nov 28, 2025
- Update AGENTS.md with new Model Management section
  - Document model types (public/user) and resolution order
  - Add key API endpoints for model operations
  - Document bind_model configuration pattern
  - Update version to 1.0.8

- Update configuring-models.md (en/zh)
  - Add model types documentation (public/user)
  - Update configuration steps for new Model Management UI
  - Add connection test feature documentation
  - Add per-task model selection documentation
  - Document model resolution priority

- Update creating-bots.md (en/zh)
  - Document bind_model as recommended model binding method
  - Make modelRef optional in spec section
  - Add bind_model_type for model type hints

- Update managing-tasks.md (en/zh)
  - Add model_id and force_override_bot_model fields
  - Document per-task model selection feature

These documentation updates reflect the changes from PR #194 which
added the model management module with unified model view,
connection testing, and per-task model selection.

Co-authored-by: qdaxb <4157870+qdaxb@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants