Skip to content

Genie 1171/story/create template system#67

Open
odaiodeh wants to merge 32 commits intomainfrom
GENIE-1171/STORY/Create-template-system
Open

Genie 1171/story/create template system#67
odaiodeh wants to merge 32 commits intomainfrom
GENIE-1171/STORY/Create-template-system

Conversation

@odaiodeh
Copy link
Collaborator

@odaiodeh odaiodeh commented Jan 31, 2026

## 📋 Templates Feature

Reusable blueprint templates with placeholder support. Users browse templates, fill required fields, and get a fully configured blueprint with resources saved to their account.

---

## 🎯 What is a Template?

```
╔═══════════════════════════════════════════════════════════════════════════════╗
║                              T E M P L A T E                                  ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║   ┌─────────────────────────────────────────────────────────────────────┐     ║
║   │                    📄 BlueprintDraft                                │     ║
║   │                                                                     │     ║
║   │   A complete, valid blueprint with placeholder default values       │     ║
║   │                                                                     │     ║
║   │   nodes:                                                            │     ║
║   │     - orchestrator_node:                                            │     ║
║   │         llm: "google_gemini"                                        │     ║
║   │         system_prompt: "PLACEHOLDER_PROMPT"  ◄── placeholder        │     ║
║   │                                                                     │     ║
║   │   llms:                                                             │     ║
║   │     - google_gemini:                                                │     ║
║   │         api_key: "PLACEHOLDER_KEY"  ◄── placeholder                 │     ║
║   │         model: "gemini-1.5-pro"                                     │     ║
║   └─────────────────────────────────────────────────────────────────────┘     ║
║                                      +                                        ║
║   ┌─────────────────────────────────────────────────────────────────────┐     ║
║   │                    🏷️ PlaceholderMeta                               │     ║
║   │                                                                     │     ║
║   │   Points to fields that need user input:                            │     ║
║   │                                                                     │     ║
║   │   • llms.google_gemini.api_key     (required)                       │     ║
║   │   • nodes.orchestrator.system_prompt (optional)                     │     ║
║   └─────────────────────────────────────────────────────────────────────┘     ║
║                                      +                                        ║
║   ┌─────────────────────────────────────────────────────────────────────┐     ║
║   │                    📋 Metadata                                      │     ║
║   │                                                                     │     ║
║   │   author, tags, version, category, is_public                        │     ║
║   └─────────────────────────────────────────────────────────────────────┘     ║
║                                                                               ║
╚═══════════════════════════════════════════════════════════════════════════════╝
```

---

## 🔄 User Journey

```
    ┌─────────┐      ┌─────────┐      ┌─────────┐      ┌─────────┐
    │ BROWSE  │      │  VIEW   │      │  FILL   │      │ CREATE  │
    │Templates│ ───► │ Schema  │ ───► │ Inputs  │ ───► │Blueprint│
    └─────────┘      └─────────┘      └─────────┘      └─────────┘
         │                │                │                │
         │                │                │                │
         ▼                ▼                ▼                ▼
    ╭─────────╮      ╭─────────╮      ╭─────────╮      ╭─────────╮
    │ list_   │      │ get_    │      │validate_│      │material-│
    │templates│      │ input_  │      │ input() │      │  ize()  │
    │   ()    │      │schema() │      │         │      │         │
    ╰─────────╯      ╰─────────╯      ╰─────────╯      ╰─────────╯
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │  ✅ Blueprint  │
                                                   │  ✅ Resources  │
                                                   │     Saved!     │
                                                   └────────────────┘
```

---

## 🔧 Materialization Pipeline

```
                            ┌──────────────────┐
                            │    USER INPUT    │
                            │  {api_key: ...}  │
                            └────────┬─────────┘
                                     │
                                     ▼
╔════════════════════════════════════════════════════════════════════════════════╗
║  STEP 1: INSTANTIATE                                                           ║
╠════════════════════════════════════════════════════════════════════════════════╣
║                                                                                ║
║   Template Draft              User Input              Merged Draft             ║
║   ┌──────────────┐      +    ┌──────────┐    =     ┌──────────────┐           ║
║   │ api_key:     │           │ api_key: │          │ api_key:     │           ║
║   │ "PLACEHOLDER"│           │ "sk-..." │          │ "sk-..."     │           ║
║   └──────────────┘           └──────────┘          └──────────────┘           ║
║                                                                                ║
║   TemplateInstantiator.instantiate()                                           ║
╚════════════════════════════════════════════════════════════════════════════════╝
                                     │
                                     ▼
╔════════════════════════════════════════════════════════════════════════════════╗
║  STEP 2: VALIDATE                                                              ║
╠════════════════════════════════════════════════════════════════════════════════╣
║                                                                                ║
║   BlueprintService.validate_draft()                                            ║
║   ✓ All configs pass Pydantic schemas                                          ║
║                                                                                ║
╚════════════════════════════════════════════════════════════════════════════════╝
                                     │
                                     ▼
╔════════════════════════════════════════════════════════════════════════════════╗
║  STEP 3: MATERIALIZE                                                           ║
╠════════════════════════════════════════════════════════════════════════════════╣
║                                                                                ║
║   ResourceMaterializer.materialize()                                           ║
║                                                                                ║
║   ┌────────────────────────────────────────────────────────────────────────┐   ║
║   │                                                                        │   ║
║   │  3a. COLLECT         3b. REMAP           3c. SAVE         3d. BUILD   │   ║
║   │  ┌─────────┐        ┌─────────┐        ┌─────────┐       ┌─────────┐  │   ║
║   │  │ Find    │   ──►  │ Update  │   ──►  │ Save to │  ──►  │ Create  │  │   ║
║   │  │ inline  │        │ Ref IDs │        │   DB    │       │ $ref    │  │   ║
║   │  │resources│        │         │        │         │       │ entries │  │   ║
║   │  └─────────┘        └─────────┘        └─────────┘       └─────────┘  │   ║
║   │                                                                        │   ║
║   │  If any step fails ──► ROLLBACK (delete saved resources)               │   ║
║   │                                                                        │   ║
║   └────────────────────────────────────────────────────────────────────────┘   ║
╚════════════════════════════════════════════════════════════════════════════════╝
                                     │
                                     ▼
╔════════════════════════════════════════════════════════════════════════════════╗
║  STEP 4: SAVE BLUEPRINT                                                        ║
╠════════════════════════════════════════════════════════════════════════════════╣
║                                                                                ║
║   BlueprintService.save_draft()                                                ║
║   → Blueprint with $ref entries saved to user's account                        ║
║                                                                                ║
╚════════════════════════════════════════════════════════════════════════════════╝
                                     │
                                     ▼
                         ┌───────────────────────┐
                         │     📦 RESULT         │
                         │                       │
                         │  blueprint_id: "bp-1" │
                         │  resource_ids: [...]  │
                         │  fields_filled: 3     │
                         └───────────────────────┘
```

---

## 🔀 Before → After Transformation

```
                    B E F O R E                              A F T E R
         (Template with inline configs)            (Materialized with $refs)

┌────────────────────────────────────────┐    ┌────────────────────────────────────────┐
│  BlueprintDraft                        │    │  BlueprintDraft                        │
│                                        │    │                                        │
│  nodes:                                │    │  nodes:                                │
│  ┌──────────────────────────────────┐  │    │  ┌──────────────────────────────────┐  │
│  │ rid: "orchestrator"              │  │    │  │ rid: "$ref:abc123"  ◄── saved   │  │
│  │ type: "orchestrator_node"        │  │    │  └──────────────────────────────────┘  │
│  │ config:                          │  │    │  ┌──────────────────────────────────┐  │
│  │   llm: LLMRef("google_gemini")   │  │ ─► │  │ rid: "user_question"             │  │
│  │   system_prompt: "You are..."    │  │    │  │ (system node - kept inline)      │  │
│  └──────────────────────────────────┘  │    │  └──────────────────────────────────┘  │
│  ┌──────────────────────────────────┐  │    │                                        │
│  │ rid: "user_question"             │  │    │  llms: []  ◄── empty (in node config) │
│  │ (system node)                    │  │    │                                        │
│  └──────────────────────────────────┘  │    │  plan:                                 │
│                                        │    │    node: "abc123"  ◄── updated         │
│  llms:                                 │    │                                        │
│  ┌──────────────────────────────────┐  │    └────────────────────────────────────────┘
│  │ rid: "google_gemini"             │  │
│  │ config:                          │  │
│  │   api_key: "sk-..."              │  │               DATABASE
│  │   model: "gemini-1.5-pro"        │  │    ┌────────────────────────────────────────┐
│  └──────────────────────────────────┘  │    │                                        │
│                                        │    │  Resource: abc123                      │
│  plan:                                 │    │    type: orchestrator_node             │
│    node: "orchestrator"                │    │    cfg: {llm: "$ref:def456", ...}     │
│                                        │    │                                        │
└────────────────────────────────────────┘    │  Resource: def456                      │
                                              │    type: google_genai                  │
                                              │    cfg: {api_key: "sk-...", ...}      │
                                              │                                        │
                                              └────────────────────────────────────────┘
```

---

## 🏗️ Architecture

```
templates/
│
├── service.py                 ◄── Public API (TemplateService)
│                                   • create, get, list, search
│                                   • get_input_schema, validate_input
│                                   • instantiate, materialize
│
├── models/
│   └── template.py            ◄── Data Models
│                                   • Template, PlaceholderMeta
│                                   • TemplateSummary, ResultModels
│
├── repository/
│   ├── repository.py          ◄── Abstract interface
│   └── mongo_repository.py    ◄── MongoDB implementation
│
├── schema/
│   └── analyzer.py            ◄── PlaceholderAnalyzer
│                                   • Creates Pydantic input models
│                                   • Generates JSON Schema for UI
│
└── instantiation/
    ├── models.py              ◄── CollectedResource, Results
    ├── instantiator.py        ◄── TemplateInstantiator
    │                               • Merges user input into draft
    └── materializer.py        ◄── ResourceMaterializer
                                    • Saves resources to DB
                                    • Creates $ref entries
```

---

## ✨ Key Design Decisions

| Decision | Why |
|----------|-----|
| 📄 **Draft is always valid** | Placeholder fields have defaults that pass Pydantic validation |
| 🏷️ **Minimal PlaceholderMeta** | Just pointers; type info from ElementRegistry at runtime |
| 🔄 **RefRemapper utility** | Clean, reusable Ref object remapping in `core/ref/` |
| ↩️ **Rollback on failure** | If save fails, all saved resources are deleted |
| 🎯 **Single rollback point** | One try-except in `materialize()` handles all failures |
| 📦 **Separated models** | Data models in `models.py`, logic in class files |
```

Summary by CodeRabbit

  • New Features

    • Full Templates experience: catalog, cards, detailed configure-&-generate view, rich field inputs, instantiation progress modal, and a Templates page in the sidebar.
  • API

    • Frontend template & session APIs and backend endpoints for listing, schema, validation, instantiate/materialize, and session creation.
  • Hooks & Validation

    • useTemplates hook with schema normalization, payload building, and instantiation flow; field validation now supports dependency-aware inputs.
  • Providers

    • MCP provider: bearer-token support and factory-based provider injection.
  • Types & Styling

    • New template types, structured error models, and expanded theme/design tokens.

@odaiodeh odaiodeh requested a review from Nirsisr as a code owner January 31, 2026 13:39
@coderabbitai
Copy link

coderabbitai bot commented Jan 31, 2026

📝 Walkthrough

Walkthrough

Adds a new Agentic Templates feature: frontend page, components, hook, API clients and types; integrates validation into workspace; theme tokens and sidebar route; backend template service, Flask endpoints, and structured template errors and materialization flows.

Changes

Cohort / File(s) Summary
Routing & Page
ui/client/src/App.tsx, ui/client/src/pages/AgenticTemplates.tsx, ui/client/src/components/layout/Sidebar.tsx
Register /templates as an Agentic route, add AgenticTemplates page and sidebar nav entry.
Frontend API & Sessions
ui/client/src/api/templates.ts, ui/client/src/api/sessions.ts
New axios wrappers for template endpoints and session creation (list/get/schema/validate/materialize, create session).
Types
ui/client/src/types/templates.ts
Add comprehensive TypeScript types for templates, schemas, validation/materialize payloads/responses, normalized UI fields, and instantiation state.
Hook
ui/client/src/hooks/use-templates.ts
New useTemplates hook: fetch/list/detail/schema, normalize JSON Schema, build payloads, validate and materialize flows, and instantiation state management.
Template UI Components
ui/client/src/components/agentic-ai/templates/*
FieldInputs.tsx, InstantiationProgress.tsx, TemplateCard.tsx, TemplateCatalog.tsx, TemplateDetailView.tsx, index.ts
Add input controls (StringArrayInput, SecretInput, FieldInput), TemplateCard, TemplateCatalog, TemplateDetailView, InstantiationProgress modal, and barrel exports.
Workspace Validation Integration
ui/client/src/components/agentic-ai/workspace/FieldRenderer.tsx, ui/client/src/components/agentic-ai/workspace/FieldValidation.tsx
Pass configValues into FieldValidation; FieldValidation now builds dependency-aware inputs, tracks a validationKey, and revalidates on dependency changes.
Styling & Theming
ui/client/src/index.css, ui/client/src/contexts/ThemeContext.tsx
Add design tokens (secondary-foreground, popover, accent, destructive, ring, radius) and generate secondary color in ThemeContext; adjust placeholder color handling.
Backend Templates Service & API
multi-agent/templates/service.py, multi-agent/api/flask/endpoints/templates.py, multi-agent/templates/errors.py
Add TemplateService (instantiate/materialize flows), structured template error types/exceptions, and Flask blueprint templates_bp exposing listing, CRUD, schema, validation, instantiate, materialize, search, and count endpoints.
MCP Provider DI & Auth
multi-agent/actions/providers/mcp/*, multi-agent/elements/providers/mcp_server_client/validator.py
Introduce McpProviderFactory-based provider creation, optional bearer_token support, and dependency-injected factories for MCP actions/validators with updated timeout handling.
Misc & Defensive Changes
multi-agent/api/flask/endpoints/{resources.py,sessions.py,blueprints.py}, ui/client/src/components/agentic-ai/templates/index.ts
Minor comments, defensive bounds, and barrel export for template components.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as AgenticTemplates Page
    participant Hook as useTemplates
    participant API as Frontend API (/templates)
    participant Server as Templates Service (Flask)
    participant DB as Backend Storage

    User->>UI: open templates catalog
    UI->>Hook: fetchTemplates()
    Hook->>API: GET /templates.list
    API->>Server: GET /templates.list
    Server->>DB: query templates
    DB-->>Server: templates[]
    Server-->>API: templates[]
    API-->>Hook: templates[]
    Hook-->>UI: render catalog

    User->>UI: select template
    UI->>Hook: fetchTemplateDetail(id)
    Hook->>API: GET /template.get and /template.schema.get
    API->>Server: GET template detail + schema
    Server->>DB: retrieve template + schema
    DB-->>Server: detail, schema
    Server-->>API: detail, schema
    API-->>Hook: detail, schema
    Hook->>Hook: normalize schema -> normalizedFields
    Hook-->>UI: show detail view

    User->>UI: submit form to instantiate
    UI->>Hook: validateTemplateInput(input)
    Hook->>API: POST /template.input.validate
    API->>Server: validate input
    Server-->>API: validation result
    alt valid
        Hook->>API: POST /template.materialize
        API->>Server: materialize request
        Server->>DB: create blueprint/resources
        DB-->>Server: blueprint_id, resources
        Server-->>API: materialize response
        API-->>Hook: success
        Hook-->>UI: show InstantiationProgress (completed)
    else invalid
        API-->>Hook: validation errors
        Hook-->>UI: show InstantiationProgress (failed)
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • sfiresht
  • Lina-AbuYousef
  • Nirsisr

Poem

🐇 I hopped through schemas, stitched each tiny part,
Cards and catalogs sprung from my heart,
Secrets tucked softly, arrays in a row,
Blueprints that bloom where ideas go,
Hop, click, create — templates ready to start!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and generic, using branch/ticket naming conventions rather than describing the actual changeset content. Replace with a clear, concise summary of the main change, e.g. 'Add template system for reusable blueprint workflows' or 'Implement template browsing, instantiation, and materialization feature'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch GENIE-1171/STORY/Create-template-system

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.

@github-actions

This comment was marked as spam.

coderabbitai[bot]

This comment was marked as abuse.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@odaiodeh
Copy link
Collaborator Author

odaiodeh commented Feb 1, 2026

@CodeRabbit review multi agent changes

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@odaiodeh I'll review the multi-agent changes in this PR. Let me first gather information about all the files changed, particularly the backend components.


🏁 Script executed:

#!/bin/bash
# List all files changed in PR `#67`
gh pr view 67 --json files --jq '.files[].path' | sort

Length of output: 2614


🏁 Script executed:

#!/bin/bash
# Get the full diff stat to understand the scope
gh pr diff 67 --stat

Length of output: 351


🏁 Script executed:

#!/bin/bash
# Look for backend template-related files
fd -e py . templates/ 2>/dev/null || echo "templates/ directory not found, searching entire repo..."
fd -e py -p 'template' .

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Search for multi-agent related code patterns
rg -n -C3 --type=py "multi.?agent|MultiAgent|multi_agent" -g '!*.pyc' -g '!__pycache__' || echo "No multi-agent patterns found"

Length of output: 29


🏁 Script executed:

#!/bin/bash
# Look for the templates backend service and models
cat backend/templates/service.py 2>/dev/null || echo "File not found"
cat backend/templates/models/template.py 2>/dev/null || echo "File not found"

Length of output: 29


🏁 Script executed:

#!/bin/bash
# Examine the template service (main public API)
echo "=== multi-agent/templates/service.py ==="
cat multi-agent/templates/service.py

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Examine template models
echo "=== multi-agent/templates/models/template.py ==="
cat multi-agent/templates/models/template.py

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Examine schema analyzer
echo "=== multi-agent/templates/schema/analyzer.py ==="
cat multi-agent/templates/schema/analyzer.py

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Examine instantiation components
echo "=== multi-agent/templates/instantiation/instantiator.py ==="
cat multi-agent/templates/instantiation/instantiator.py
echo -e "\n=== multi-agent/templates/instantiation/materializer.py ==="
cat multi-agent/templates/instantiation/materializer.py
echo -e "\n=== multi-agent/templates/instantiation/models.py ==="
cat multi-agent/templates/instantiation/models.py

Length of output: 421


🏁 Script executed:

#!/bin/bash
# Examine repository layer
echo "=== multi-agent/templates/repository/repository.py ==="
cat multi-agent/templates/repository/repository.py
echo -e "\n=== multi-agent/templates/repository/mongo_repository.py ==="
cat multi-agent/templates/repository/mongo_repository.py

Length of output: 280


🏁 Script executed:

#!/bin/bash
# Examine API endpoints
echo "=== multi-agent/api/flask/endpoints/templates.py ==="
cat multi-agent/api/flask/endpoints/templates.py

Length of output: 129


🏁 Script executed:

#!/bin/bash
# Examine resources system changes
echo "=== multi-agent/resources/service.py ==="
cat multi-agent/resources/service.py
echo -e "\n=== multi-agent/resources/models.py ==="
cat multi-agent/resources/models.py

Length of output: 210


🏁 Script executed:

#!/bin/bash
# Examine core ref remapper and related utilities
echo "=== multi-agent/core/ref/remapper.py ==="
cat multi-agent/core/ref/remapper.py
echo -e "\n=== multi-agent/core/ref/models.py ==="
cat multi-agent/core/ref/models.py

Length of output: 208


🏁 Script executed:

#!/bin/bash
# Check the READMEs for design documentation
echo "=== multi-agent/templates/README.md ==="
cat multi-agent/templates/README.md
echo -e "\n=== multi-agent/templates/instantiation/README.md ==="
cat multi-agent/templates/instantiation/README.md

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Examine MCP provider changes (bearer token addition)
echo "=== multi-agent/elements/providers/mcp_server_client/mcp_provider.py ==="
cat multi-agent/elements/providers/mcp_server_client/mcp_provider.py

Length of output: 169


🏁 Script executed:

#!/bin/bash
# Get the PR diff to review the actual file changes
gh pr diff 67 > /tmp/pr_diff.txt
wc -l /tmp/pr_diff.txt
echo "Extracting first 500 lines of diff..."
head -500 /tmp/pr_diff.txt

Length of output: 19662


🏁 Script executed:

#!/bin/bash
# Continue showing more of the diff
echo "Lines 501-1000 of diff..."
sed -n '501,1000p' /tmp/pr_diff.txt

Length of output: 17371


🏁 Script executed:

#!/bin/bash
# Focus on backend template service
echo "=== Template Service ==="
grep -A 100 "diff --git a/multi-agent/templates/service.py" /tmp/pr_diff.txt | head -150

Length of output: 3305


🏁 Script executed:

#!/bin/bash
# Focus on template models
echo "=== Template Models ==="
grep -A 100 "diff --git a/multi-agent/templates/models/template.py" /tmp/pr_diff.txt | head -150

Length of output: 3283


🏁 Script executed:

#!/bin/bash
# Focus on schema analyzer
echo "=== Schema Analyzer ==="
grep -A 150 "diff --git a/multi-agent/templates/schema/analyzer.py" /tmp/pr_diff.txt | head -200

Length of output: 5460


🏁 Script executed:

#!/bin/bash
# Focus on instantiation components
echo "=== Instantiator ==="
grep -A 100 "diff --git a/multi-agent/templates/instantiation/instantiator.py" /tmp/pr_diff.txt | head -150

echo -e "\n=== Materializer ==="
grep -A 150 "diff --git a/multi-agent/templates/instantiation/materializer.py" /tmp/pr_diff.txt | head -200

Length of output: 10366


🏁 Script executed:

#!/bin/bash
# Focus on API endpoints
echo "=== API Endpoints ==="
grep -A 150 "diff --git a/multi-agent/api/flask/endpoints/templates.py" /tmp/pr_diff.txt | head -200

Length of output: 5143


🏁 Script executed:

#!/bin/bash
# Focus on resources system
echo "=== Resources Service ==="
grep -A 100 "diff --git a/multi-agent/resources/service.py" /tmp/pr_diff.txt | head -150

echo -e "\n=== Resources Models ==="
grep -A 50 "diff --git a/multi-agent/resources/models.py" /tmp/pr_diff.txt | head -100

Length of output: 7473


🏁 Script executed:

#!/bin/bash
# Focus on ref remapper
echo "=== Ref Remapper ==="
grep -A 100 "diff --git a/multi-agent/core/ref/remapper.py" /tmp/pr_diff.txt | head -150

Length of output: 3930


🏁 Script executed:

#!/bin/bash
# Get README content
echo "=== Templates README ==="
grep -A 200 "diff --git a/multi-agent/templates/README.md" /tmp/pr_diff.txt | head -250

Length of output: 11532


@odaiodeh I've completed a comprehensive review of the multi-agent changes in PR #67. This is an impressive feature addition! Here's my analysis:

🎯 Architecture Overview

The Templates system adds reusable blueprint templates with excellent separation of concerns:

Template = BlueprintDraft + PlaceholderMeta + Metadata
           ↓
User Input + Validation
           ↓
Instantiation → Materialization → Saved Blueprint + Resources

✅ Strengths

1. SOLID Principles Throughout

  • Single Responsibility: Each class has one clear purpose (TemplateService orchestrates, PlaceholderAnalyzer handles schema, TemplateInstantiator merges data)
  • Dependency Inversion: Services depend on repository abstractions, not concrete implementations
  • Open/Closed: Extensible via ElementRegistry without modifying core code

2. Clean Separation of Layers

┌─────────────────────┐
│   API Endpoints     │  Flask routes
├─────────────────────┤
│  Template Service   │  Orchestration layer
├─────────────────────┤
│  Business Logic     │  Instantiator, Materializer, Analyzer
├─────────────────────┤
│   Repository        │  MongoDB persistence
└─────────────────────┘

3. Rollback-on-Failure Pattern

The ResourceMaterializer.materialize() correctly implements single rollback point:

saved_rids: List[str] = []
try:
    saved_rids = self._save_resources(collected, user_id)
    final_draft = self._build_final_draft(draft, id_mapping)
except Exception as e:
    self._rollback(saved_rids)  # ✅ Cleans up on failure
    raise MaterializationError(...)

4. Dynamic Pydantic Model Creation

The PlaceholderAnalyzer cleverly extracts field definitions from original schemas and builds nested input models - this is a sophisticated use of Pydantic's create_model().

⚠️ Critical Issues

1. Commented Exception Handling 🚨

File: multi-agent/api/flask/endpoints/templates.py (Line 331-356)

The materialize_template() endpoint has ALL exception handling commented out:

def materialize_template(...):
    # try:
    svc = current_app.container.template_service
    result = svc.materialize(...)
    return jsonify({...}), 201
    # except TemplateNotFoundError as e:
    #     return jsonify({"error": str(e)}), 404
    # except InstantiationError as e:
    #     ...

Impact: Any error will crash the server with a 500 and expose stack traces to clients.

Recommendation: Uncomment the exception handling immediately. This is a production-critical bug.

def materialize_template(...):
    try:
        svc = current_app.container.template_service
        result = svc.materialize(...)
        return jsonify({"status": "success", **result.model_dump()}), 201
    except TemplateNotFoundError as e:
        return jsonify({"error": str(e)}), 404
    except InstantiationError as e:
        return jsonify({
            "error": str(e),
            "errors": e.to_dict_list(),
        }), 400
    except MaterializationError as e:
        return jsonify({
            "error": str(e),
            "errors": e.to_dict_list(),
        }), 400
    except RuntimeError as e:
        return jsonify({"error": str(e)}), 500
    except Exception as e:
        logger.exception(f"Error materializing template {template_id}")
        return jsonify({"error": str(e)}), 500

2. Breaking Change: Resource Model Rename

Files: multi-agent/resources/models.py, multi-agent/resources/service.py

ResourceDocResource rename might break existing code.

Recommendation: Search for all usages:

rg "ResourceDoc" --type=py

3. MCP Bearer Token Security

Files: multi-agent/actions/providers/mcp/*/ and multi-agent/elements/providers/mcp_server_client/*

Bearer tokens are being passed as strings. Ensure they're:

  • Stored securely (not in logs)
  • Transmitted over HTTPS only
  • Marked with SecretHint in schema (✅ already done in validate_connection.py)

💡 Suggestions for Improvement

1. Add Input Validation to Repository Layer

Currently, the MongoDB repository trusts the service layer. Consider adding validation:

class MongoTemplateRepository(TemplateRepository):
    def create(self, template: Template) -> str:
        # Add existence check
        if self.exists(template.template_id):
            raise TemplateSaveError(f"Template {template.template_id} already exists")
        # ... rest of implementation

2. Consider Caching for Input Schemas

The PlaceholderAnalyzer.create_input_model() is expensive. Cache results:

from functools import lru_cache

class PlaceholderAnalyzer:
    `@lru_cache`(maxsize=128)
    def create_input_model(self, template_id: str, template: Template) -> Type[BaseModel]:
        # ... existing logic

3. Add Pagination to list_templates

The API accepts skip/limit but should return pagination metadata:

def list_templates(...):
    summaries = svc.list_template_summaries(...)
    total = svc.count_templates(is_public=is_public, category=category)
    return jsonify({
        "templates": [s.model_dump(mode="json") for s in summaries],
        "pagination": {
            "skip": skip,
            "limit": limit,
            "total": total,
            "has_more": skip + len(summaries) < total
        }
    }), 200

4. Frontend: Error Boundary

Add error boundaries around the templates UI to gracefully handle failures:

<ErrorBoundary fallback={<TemplateErrorState />}>
  <AgenticTemplates />
</ErrorBoundary>

5. Add Template Versioning

The TemplateMetadata has a version field but no versioning logic. Consider:

  • Version history tracking
  • Migration between template versions
  • Deprecation warnings

🎨 Frontend Review

Strengths:

  • ✅ Clean component hierarchy (Catalog → Detail → Progress)
  • ✅ Proper form state management with validation
  • ✅ Loading states and error handling
  • ✅ AnimatePresence for smooth transitions
  • ✅ Comprehensive field input components (StringArray, Secret, etc.)

Suggestions:

  1. Debounce validation calls in FieldValidation.tsx to reduce API load
  2. Add optimistic UI updates when materializing templates
  3. Consider virtualization for large template catalogs (react-window)

📝 Documentation

The READMEs are excellent! Well-structured with ASCII diagrams explaining:

  • Core concepts
  • Flow diagrams
  • Directory structure
  • Key design decisions

🧪 Testing Recommendations

  1. Unit Tests:

    • PlaceholderAnalyzer field extraction
    • TemplateInstantiator merge logic
    • RefRemapper reference replacement
  2. Integration Tests:

    • Full materialize flow with rollback scenarios
    • Concurrent resource saves
    • Invalid input handling
  3. Frontend Tests:

    • Form validation logic
    • Navigation flows
    • Error state rendering

📊 Performance Considerations

  1. ResourceMaterializer saves resources sequentially - consider batching:
def _save_resources_batch(self, collected: List[CollectedResource], user_id: str):
    # Prepare all resources
    resources = [self._prepare_resource(item, user_id) for item in collected]
    # Batch insert if MongoDB supports it
    return self._resources.save_resources_batch(resources)
  1. PlaceholderAnalyzer builds models every time - add caching (noted above)

  2. Frontend: Large template lists - implement virtual scrolling

🔒 Security Review

Good practices observed:

  • Bearer tokens marked as secrets
  • Input validation via Pydantic
  • User isolation (user_id checks)

⚠️ Additional recommendations:

  • Add rate limiting to materialize endpoint
  • Validate template ownership before operations
  • Sanitize template names to prevent injection

Summary

This is a well-architected, production-quality feature with excellent separation of concerns and SOLID principles. The main blocker is the commented exception handling in the materialize endpoint, which must be fixed before merge.

Estimated Review Effort: High (due to scope)
Code Quality: Excellent
Architecture: Outstanding
Blocker Issues: 1 (commented exception handling)

Would you like me to create an issue to track the exception handling fix and other improvements?

coderabbitai[bot]

This comment was marked as abuse.

Nirsisr and others added 3 commits February 1, 2026 02:53
- Code improvment | API call from external component instead of inline
- Reset Logic | Change
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ates' into GENIE-1171/STORY/Create-template-system
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

🤖 Fix all issues with AI agents
In `@multi-agent/api/flask/endpoints/templates.py`:
- Around line 28-58: The endpoint list_templates accepts a tags query param but
never uses it; update the call to svc.list_template_summaries in list_templates
to pass tags=tags (or transform tags as needed) and propagate that parameter
through the template_service method(s) (e.g.,
TemplateService.list_template_summaries and any downstream repository/query
functions) so tag filtering is actually applied; ensure signatures and callers
are updated to accept and use the tags argument.
- Around line 300-355: The endpoint materialize_template currently trusts the
body-supplied user_id; update it to derive and validate the caller from the
authentication context before calling
current_app.container.template_service.materialize: retrieve the authenticated
principal (e.g. from Flask's request context, session, or current_user), if an
authenticated id exists use that as the effective user_id, and if the request
body includes a user_id ensure it exactly matches the authenticated id
(otherwise return 401/403); then pass the validated/derived user_id into
svc.materialize and keep existing exception handling. Ensure the same validation
pattern is applied consistently to other handlers that accept a user_id
parameter.

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

🤖 Fix all issues with AI agents
In `@multi-agent/templates/errors.py`:
- Around line 88-102: The to_dict_list serialization in MaterializationError
currently calls e.model_dump() which can leave Enum fields as objects; update
MaterializationError.to_dict_list to call e.model_dump(mode="json") so Enum
values are serialized as JSON-safe strings, and make the identical change for
the MergeError/merge error serialization path (e.g., any MergeError.to_dict_list
or places calling model_dump on MergeFieldError/MergeError) to use
model_dump(mode="json") as well to ensure error_type Enums become strings in API
responses.
- Around line 120-144: Change the InstantiationError constructor signature to
use an explicit Optional[List[Any]] for the errors parameter (update the typing
import if necessary) and ensure _serialize calls model_dump(mode="json") when
serializing Pydantic models; keep the existing to_dict_list and _serialize flow
but replace model_dump() with model_dump(mode="json") and update the type hint
on __init__ (errors: Optional[List[Any]] = None).
- Around line 33-47: MergeError.to_dict_list currently calls e.model_dump()
without specifying JSON mode; update MergeError.to_dict_list and the analogous
MaterializationError.to_dict_list to call e.model_dump(mode="json") so
enum/complex types are serialized consistently for API responses. Locate the
methods named to_dict_list on the MergeError and MaterializationError classes
and change their model_dump() invocations to model_dump(mode="json") to match
the established serialization pattern.

In `@multi-agent/templates/service.py`:
- Around line 269-327: In materialize, ensure instantiation errors keep the
documented InstantiationError contract and that resource intent isn't silently
ignored: wrap the call to self._instantiator.instantiate(...) in a try/except
that catches MergeError and re-raises it as InstantiationError (preserving the
original exception as the __cause__), and before calling self._save_blueprint
check if save_resources is True and self._resources_service is None and raise a
RuntimeError (e.g. "ResourcesService not configured") instead of letting
_save_blueprint silently skip or proceed; references: materialize,
_instantiator.instantiate, MergeError, InstantiationError, _resources_service,
and _save_blueprint.
🧹 Nitpick comments (1)
multi-agent/templates/service.py (1)

212-237: Avoid catching broad exceptions during validation.
Catching Exception can mask unexpected failures. Prefer handling ValidationError only and let other errors bubble.

🔧 Proposed fix
+from pydantic import ValidationError
@@
-        try:
-            input_model(**user_input)
-            return InputValidationResult(is_valid=True, errors=[])
-        except Exception as e:
-            return InputValidationResult(is_valid=False, errors=[str(e)])
+        try:
+            input_model(**user_input)
+            return InputValidationResult(is_valid=True, errors=[])
+        except ValidationError as e:
+            return InputValidationResult(is_valid=False, errors=[str(e)])

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: 1

🤖 Fix all issues with AI agents
In `@multi-agent/api/flask/endpoints/templates.py`:
- Around line 184-216: The delete_template endpoint on templates_bp currently
performs no authorization; before calling
current_app.container.template_service.exists or .delete_template you must
enforce access control: require an auth decorator or retrieve the authenticated
user (e.g., current_user or current_app.container.auth.get_current_user()) and
validate that the user is authorized to delete the template (owner,
has_role('admin'), or permitted by template_service.check_permission). If using
middleware, apply the auth decorator to templates_bp.route("/template.delete")
or, if enforcing inline, fetch the user and call a permission check on
template_service (e.g., template_service.user_can_delete(template_id, user_id))
and return 403 when unauthorized; only then call
svc.delete_template(template_id).
🧹 Nitpick comments (5)
multi-agent/templates/service.py (4)

101-102: datetime.utcnow() is deprecated.

datetime.utcnow() is deprecated as of Python 3.12 and returns a naive datetime. Consider using datetime.now(timezone.utc) for timezone-aware timestamps.

♻️ Suggested fix
+from datetime import datetime, timezone
-from datetime import datetime
...
-            created_at=datetime.utcnow(),
-            updated_at=datetime.utcnow(),
+            created_at=datetime.now(timezone.utc),
+            updated_at=datetime.now(timezone.utc),

113-129: Add exception chaining with from err.

When re-raising exceptions, use from err to preserve the original traceback for debugging. This is flagged by Ruff B904.

♻️ Suggested fix
     def get_template(self, template_id: str) -> Template:
         try:
             return self._repo.get(template_id)
-        except KeyError:
-            raise TemplateNotFoundError(template_id)
+        except KeyError as err:
+            raise TemplateNotFoundError(template_id) from err

     def update_template(self, template: Template) -> bool:
         try:
-            template.updated_at = datetime.utcnow()
+            template.updated_at = datetime.now(timezone.utc)
             return self._repo.update(template)
-        except KeyError:
-            raise TemplateNotFoundError(template.template_id)
+        except KeyError as err:
+            raise TemplateNotFoundError(template.template_id) from err

233-237: Consider catching pydantic.ValidationError specifically.

Catching bare Exception here works but is overly broad. Since input_model is a Pydantic model, catching pydantic.ValidationError would be more precise and avoid masking unexpected errors.

♻️ Suggested fix
+from pydantic import ValidationError
...
         try:
             input_model(**user_input)
             return InputValidationResult(is_valid=True, errors=[])
-        except Exception as e:
+        except ValidationError as e:
             return InputValidationResult(is_valid=False, errors=[str(e)])

259-264: Add exception chaining with from err.

Preserve the original exception context for better debugging. Also, use {e!s} instead of {str(e)} for the conversion.

♻️ Suggested fix
         try:
             return self._instantiator.instantiate(template, user_input)
         except MergeError as e:
-            raise InstantiationError(str(e), errors=e.errors)
+            raise InstantiationError(str(e), errors=e.errors) from e
         except Exception as e:
-            raise InstantiationError(f"Instantiation failed: {str(e)}")
+            raise InstantiationError(f"Instantiation failed: {e!s}") from e
multi-agent/api/flask/endpoints/templates.py (1)

250-250: Parameter input shadows Python built-in.

The parameter name input shadows Python's built-in input() function. Consider renaming to user_input or input_data for clarity. This pattern also appears in instantiate_template and materialize_template.

♻️ Suggested fix for this endpoint
-def validate_template_input(template_id, input):
+def validate_template_input(template_id, user_input):
     ...
-        result = svc.validate_input(template_id, input)
+        result = svc.validate_input(template_id, user_input)

Note: The @from_body decorator uses "input" as the key, which is fine for the JSON field name. Only the Python parameter needs renaming.

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: 1

🤖 Fix all issues with AI agents
In `@multi-agent/templates/service.py`:
- Around line 131-137: delete_template currently calls
self._repo.delete(template_id) directly but does not catch the KeyError the
repository can raise; wrap the call in a try/except around
TemplateService.delete_template and catch KeyError from
TemplateRepository.delete, then raise TemplateNotFoundError(template_id) (same
pattern used in get_template/update_template) so not-found cases map to
TemplateNotFoundError instead of leaking a 500.
🧹 Nitpick comments (2)
multi-agent/templates/service.py (2)

259-264: Use exception chaining to preserve root cause when wrapping instantiation errors.

Exception chaining with raise ... from e preserves the original exception in the traceback for debugging without changing the public error contract (InstantiationError).

♻️ Proposed fix
         try:
             return self._instantiator.instantiate(template, user_input)
         except MergeError as e:
-            raise InstantiationError(str(e), errors=e.errors)
+            raise InstantiationError(str(e), errors=e.errors) from e
         except Exception as e:
-            raise InstantiationError(f"Instantiation failed: {str(e)}")
+            raise InstantiationError(f"Instantiation failed: {e}") from e

233-237: Narrow validation exceptions to Pydantic ValidationError.

Catching Exception masks validation failures as user input errors when they should be distinguished from unexpected internal errors. Since input_model(**user_input) only raises ValidationError on invalid input, narrow the exception handler to catch only that. This allows other errors to bubble up for proper logging and debugging.

♻️ Proposed fix
+from pydantic import ValidationError
@@
         try:
             input_model(**user_input)
             return InputValidationResult(is_valid=True, errors=[])
-        except Exception as e:
-            return InputValidationResult(is_valid=False, errors=[str(e)])
+        except ValidationError as e:
+            return InputValidationResult(
+                is_valid=False,
+                errors=[err.get("msg", str(err)) for err in e.errors()],
+            )

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: 1

🤖 Fix all issues with AI agents
In `@multi-agent/api/flask/endpoints/resources.py`:
- Around line 161-206: The route handler validate_resources currently caps
max_workers with min(max_workers, 20) but doesn't prevent zero or negative
values; change the clamping so max_workers is at least 1 and at most 20 (e.g.
set max_workers = max(1, min(max_workers, 20)) or check if max_workers <= 0 then
assign 1) before calling svc.validate_resources to avoid breaking executor setup
downstream.
🧹 Nitpick comments (2)
multi-agent/api/flask/endpoints/sessions.py (1)

142-157: Acknowledged: Authorization check is missing for session deletion.

The TODO correctly identifies a security gap. Currently, any caller with a valid session_id can delete sessions without ownership verification. This should be addressed before production deployment.

To implement authorization, consider:

  1. Adding a user_id parameter to the endpoint (similar to other session endpoints)
  2. Verifying the session belongs to the requesting user before deletion
  3. Returning 403 Forbidden if the user doesn't own the session

Would you like me to generate a proposed implementation for the authorization check, or open a new issue to track this work?

multi-agent/templates/service.py (1)

216-242: Narrow validation exception handling to ValidationError.
Catching all exceptions hides internal failures (e.g., schema construction issues). Prefer catching Pydantic’s validation errors and let unexpected errors surface.

🛠️ Suggested fix
+from pydantic import ValidationError
@@
-        try:
-            input_model(**user_input)
-            return InputValidationResult(is_valid=True, errors=[])
-        except Exception as e:
-            return InputValidationResult(is_valid=False, errors=[str(e)])
+        try:
+            input_model(**user_input)
+            return InputValidationResult(is_valid=True, errors=[])
+        except ValidationError as e:
+            return InputValidationResult(is_valid=False, errors=[str(e)])

Nirsisr and others added 4 commits February 1, 2026 12:40
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…hat-community-ai-tools/UnifAI into GENIE-1167/EPIC/Agentic-AI-Templates
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: 1

🤖 Fix all issues with AI agents
In `@multi-agent/api/flask/endpoints/resources.py`:
- Around line 107-125: The delete_resource endpoint currently deletes by
resource_id without verifying ownership; update it to enforce that the
requesting user owns the resource by obtaining the current user's id from the
request context (e.g., g.current_user.id or your auth container) and either (A)
call resources_service.delete(resource_id, user_id) and implement the ownership
check inside ResourcesService.delete (compare fetched_resource.user_id to
user_id and raise a 403/AuthorizationError if mismatched), or (B) fetch the
resource via resources_service.get(resource_id) in the endpoint, verify
resource.user_id matches the current user id, and only then call
resources_service.delete(resource_id); ensure to return 403 on unauthorized
attempts and keep existing error handling for
ResourceInUseError/KeyError/Exception.
🧹 Nitpick comments (1)
multi-agent/api/flask/endpoints/resources.py (1)

19-29: Avoid returning raw exception messages in 500 responses.

Returning str(e) can leak internal details. Prefer logging server-side and returning a generic error; consider applying the same pattern across endpoints for consistency.

Suggested pattern
-    except Exception as e:
-        return jsonify({"error": str(e)}), 500
+    except Exception:
+        current_app.logger.exception("save_resource failed")
+        return jsonify({"error": "Internal server error"}), 500

…ates' into GENIE-1171/STORY/Create-template-system
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

🤖 Fix all issues with AI agents
In `@ui/client/src/components/agentic-ai/templates/FieldInputs.tsx`:
- Around line 81-93: The remove icon button inside the Badge is missing an
accessible name; update the button rendered next to {item} (the element with
onClick={() => handleRemove(index)}) to provide an accessible label by adding an
aria-label (e.g., aria-label={`Remove ${item}`}) or by including a
visually-hidden text node describing the action tied to the X icon component,
ensuring screen readers can identify the control.
- Around line 119-125: The button that toggles secret visibility (uses
showSecret, setShowSecret and renders Eye/EyeOff) is icon-only and lacks an
accessible name; update the button to provide a dynamic accessible label such as
aria-label={showSecret ? "Hide secret" : "Show secret"} (or aria-pressed and an
appropriate aria-label) so screen readers announce its purpose, and keep the
existing onClick behavior and visual icons (Eye/EyeOff) intact.
- Around line 184-194: The numeric case in FieldInputs.tsx currently converts
the input via Number(e.target.value) which can produce NaN for intermediate
inputs; update the onChange handler for the Input component (the case 'number'
branch) to parse the string safely: trim the input, attempt conversion, check
Number.isFinite or !Number.isNaN before calling onChange, and if the parse
yields NaN or the string is empty/invalid, call onChange(undefined) (or preserve
previous valid state if desired). Ensure you reference the Input component's
onChange and the surrounding field/value props so invalid numeric entries never
propagate NaN into payloads.

In `@ui/client/src/hooks/use-templates.ts`:
- Around line 338-353: The code treats any HTTP 200 response from
materializeTemplate as success without checking the MaterializeResponse.status
field; update the post-call logic in the function that calls materializeTemplate
to inspect result.status (from MaterializeResponse) and only mark success when
result.status === 'success'. On error (result.status === 'error')
setInstantiationStatus('failed'), setInstantiationResult(result) or null as
appropriate, show an error toast with result.message or a descriptive message,
and throw or return an error to halt further success handling (do not show the
current success toast). Ensure you update the branches around
setInstantiationStatus, setInstantiationResult, toast, and the returned value so
they depend on result.status.
🧹 Nitpick comments (1)
ui/client/src/hooks/use-templates.ts (1)

305-311: Clear prior validation state before starting a new materialization.

Without resetting, stale errors can persist across retries.

🛠️ Suggested fix
     try {
       setInstantiationStatus('validating');
       setError(null);
+      setValidationErrors([]);
+      setElementValidationResults([]);
+      setInstantiationResult(null);

Comment on lines +81 to +93
<Badge
key={index}
variant="secondary"
className="bg-primary/20 text-primary border-primary/30 flex items-center gap-1"
>
{item}
<button
type="button"
onClick={() => handleRemove(index)}
className="ml-1 hover:text-red-400"
>
<X className="h-3 w-3" />
</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

Add an accessible label to the remove icon button.

The icon-only remove control has no accessible name, which makes it opaque to screen readers.

🛠️ Suggested fix
               <button
                 type="button"
                 onClick={() => handleRemove(index)}
                 className="ml-1 hover:text-red-400"
+                aria-label={`Remove ${item}`}
               >
-                <X className="h-3 w-3" />
+                <X className="h-3 w-3" aria-hidden="true" />
               </button>
📝 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
<Badge
key={index}
variant="secondary"
className="bg-primary/20 text-primary border-primary/30 flex items-center gap-1"
>
{item}
<button
type="button"
onClick={() => handleRemove(index)}
className="ml-1 hover:text-red-400"
>
<X className="h-3 w-3" />
</button>
<Badge
key={index}
variant="secondary"
className="bg-primary/20 text-primary border-primary/30 flex items-center gap-1"
>
{item}
<button
type="button"
onClick={() => handleRemove(index)}
className="ml-1 hover:text-red-400"
aria-label={`Remove ${item}`}
>
<X className="h-3 w-3" aria-hidden="true" />
</button>
🤖 Prompt for AI Agents
In `@ui/client/src/components/agentic-ai/templates/FieldInputs.tsx` around lines
81 - 93, The remove icon button inside the Badge is missing an accessible name;
update the button rendered next to {item} (the element with onClick={() =>
handleRemove(index)}) to provide an accessible label by adding an aria-label
(e.g., aria-label={`Remove ${item}`}) or by including a visually-hidden text
node describing the action tied to the X icon component, ensuring screen readers
can identify the control.

Comment on lines +119 to +125
<button
type="button"
onClick={() => setShowSecret(!showSecret)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white"
>
{showSecret ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</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

Add an accessible label to the show/hide secret toggle.

This is another icon-only control without a readable name for assistive tech.

🛠️ Suggested fix
       <button
         type="button"
         onClick={() => setShowSecret(!showSecret)}
         className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white"
+        aria-label={showSecret ? 'Hide secret' : 'Show secret'}
+        aria-pressed={showSecret}
       >
-        {showSecret ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
+        {showSecret ? <EyeOff className="h-4 w-4" aria-hidden="true" /> : <Eye className="h-4 w-4" aria-hidden="true" />}
       </button>
📝 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
type="button"
onClick={() => setShowSecret(!showSecret)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white"
>
{showSecret ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
<button
type="button"
onClick={() => setShowSecret(!showSecret)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white"
aria-label={showSecret ? 'Hide secret' : 'Show secret'}
aria-pressed={showSecret}
>
{showSecret ? <EyeOff className="h-4 w-4" aria-hidden="true" /> : <Eye className="h-4 w-4" aria-hidden="true" />}
</button>
🤖 Prompt for AI Agents
In `@ui/client/src/components/agentic-ai/templates/FieldInputs.tsx` around lines
119 - 125, The button that toggles secret visibility (uses showSecret,
setShowSecret and renders Eye/EyeOff) is icon-only and lacks an accessible name;
update the button to provide a dynamic accessible label such as
aria-label={showSecret ? "Hide secret" : "Show secret"} (or aria-pressed and an
appropriate aria-label) so screen readers announce its purpose, and keep the
existing onClick behavior and visual icons (Eye/EyeOff) intact.

Comment on lines +184 to +194
case 'number':
return (
<Input
type="number"
value={value ?? ''}
onChange={(e) => onChange(e.target.value ? Number(e.target.value) : undefined)}
placeholder={`Enter ${field.label.toLowerCase()}`}
className="bg-background-dark border-gray-700"
min={field.minimum}
max={field.maximum}
/>
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

Guard against NaN when parsing numeric input.

Number(e.target.value) can produce NaN (e.g., user types - or e), which then propagates into payloads.

🛠️ Suggested fix
         <Input
           type="number"
           value={value ?? ''}
-          onChange={(e) => onChange(e.target.value ? Number(e.target.value) : undefined)}
+          onChange={(e) => {
+            if (e.target.value === '') return onChange(undefined);
+            const parsed = Number(e.target.value);
+            onChange(Number.isFinite(parsed) ? parsed : undefined);
+          }}
           placeholder={`Enter ${field.label.toLowerCase()}`}
           className="bg-background-dark border-gray-700"
           min={field.minimum}
           max={field.maximum}
         />
📝 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
case 'number':
return (
<Input
type="number"
value={value ?? ''}
onChange={(e) => onChange(e.target.value ? Number(e.target.value) : undefined)}
placeholder={`Enter ${field.label.toLowerCase()}`}
className="bg-background-dark border-gray-700"
min={field.minimum}
max={field.maximum}
/>
case 'number':
return (
<Input
type="number"
value={value ?? ''}
onChange={(e) => {
if (e.target.value === '') return onChange(undefined);
const parsed = Number(e.target.value);
onChange(Number.isFinite(parsed) ? parsed : undefined);
}}
placeholder={`Enter ${field.label.toLowerCase()}`}
className="bg-background-dark border-gray-700"
min={field.minimum}
max={field.maximum}
/>
🤖 Prompt for AI Agents
In `@ui/client/src/components/agentic-ai/templates/FieldInputs.tsx` around lines
184 - 194, The numeric case in FieldInputs.tsx currently converts the input via
Number(e.target.value) which can produce NaN for intermediate inputs; update the
onChange handler for the Input component (the case 'number' branch) to parse the
string safely: trim the input, attempt conversion, check Number.isFinite or
!Number.isNaN before calling onChange, and if the parse yields NaN or the string
is empty/invalid, call onChange(undefined) (or preserve previous valid state if
desired). Ensure you reference the Input component's onChange and the
surrounding field/value props so invalid numeric entries never propagate NaN
into payloads.

Comment on lines +338 to +353
const result = await materializeTemplate({
templateId,
userId,
input,
blueprintName
});

setInstantiationStatus('completed');
setInstantiationResult(result);

toast({
title: 'Success',
description: `Blueprint "${result.name}" created successfully!`
});

return result;
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

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the file
find . -name "use-templates.ts" -type f

Repository: redhat-community-ai-tools/UnifAI

Length of output: 114


🏁 Script executed:

# Let's also look at the general structure of the src directory
ls -la ui/client/src/

Repository: redhat-community-ai-tools/UnifAI

Length of output: 1166


🏁 Script executed:

# Now let's read the use-templates.ts file to see the code at lines 338-353
wc -l ui/client/src/hooks/use-templates.ts

Repository: redhat-community-ai-tools/UnifAI

Length of output: 116


🏁 Script executed:

# Read the file around lines 338-353
sed -n '330,360p' ui/client/src/hooks/use-templates.ts

Repository: redhat-community-ai-tools/UnifAI

Length of output: 806


🏁 Script executed:

# Search for materializeTemplate function definition
rg -n "materializeTemplate" ui/client/src/ --type typescript --type tsx -A 3

Repository: redhat-community-ai-tools/UnifAI

Length of output: 110


🏁 Script executed:

# Search for materializeTemplate function definition without specifying file type
rg -n "materializeTemplate" ui/client/src/ -A 3

Repository: redhat-community-ai-tools/UnifAI

Length of output: 1040


🏁 Script executed:

# Now let's find the MaterializeResponse type definition
rg -n "MaterializeResponse\|MaterializeParams" ui/client/src/api/templates.ts -B 2 -A 10

Repository: redhat-community-ai-tools/UnifAI

Length of output: 58


🏁 Script executed:

# Let's read the api/templates.ts file to see the type definitions
cat -n ui/client/src/api/templates.ts

Repository: redhat-community-ai-tools/UnifAI

Length of output: 3421


🏁 Script executed:

# Now let's find the MaterializeResponse type definition
cat -n ui/client/src/types/templates.ts

Repository: redhat-community-ai-tools/UnifAI

Length of output: 6920


Check the status field in the API response—the backend can return status: 'error' with HTTP 200.

The MaterializeResponse interface defines status: 'success' | 'error'. The current code treats any HTTP 200 response as success without checking this field, so users will see a success toast even if the operation failed.

🛠️ Suggested fix
       const result = await materializeTemplate({
         templateId,
         userId,
         input,
         blueprintName
       });
 
+      if (result.status === 'error') {
+        setError('Failed to materialize template');
+        setInstantiationStatus('failed');
+        toast({
+          title: 'Error',
+          description: 'Failed to materialize template',
+          variant: 'destructive'
+        });
+        return null;
+      }
+
       setInstantiationStatus('completed');
       setInstantiationResult(result);
📝 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
const result = await materializeTemplate({
templateId,
userId,
input,
blueprintName
});
setInstantiationStatus('completed');
setInstantiationResult(result);
toast({
title: 'Success',
description: `Blueprint "${result.name}" created successfully!`
});
return result;
const result = await materializeTemplate({
templateId,
userId,
input,
blueprintName
});
if (result.status === 'error') {
setError('Failed to materialize template');
setInstantiationStatus('failed');
toast({
title: 'Error',
description: 'Failed to materialize template',
variant: 'destructive'
});
return null;
}
setInstantiationStatus('completed');
setInstantiationResult(result);
toast({
title: 'Success',
description: `Blueprint "${result.name}" created successfully!`
});
return result;
🤖 Prompt for AI Agents
In `@ui/client/src/hooks/use-templates.ts` around lines 338 - 353, The code treats
any HTTP 200 response from materializeTemplate as success without checking the
MaterializeResponse.status field; update the post-call logic in the function
that calls materializeTemplate to inspect result.status (from
MaterializeResponse) and only mark success when result.status === 'success'. On
error (result.status === 'error') setInstantiationStatus('failed'),
setInstantiationResult(result) or null as appropriate, show an error toast with
result.message or a descriptive message, and throw or return an error to halt
further success handling (do not show the current success toast). Ensure you
update the branches around setInstantiationStatus, setInstantiationResult,
toast, and the returned value so they depend on result.status.

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: 1

Caution

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

⚠️ Outside diff range comments (1)
multi-agent/actions/providers/mcp/get_tools_names/get_tools_names.py (1)

2-18: ⚠️ Potential issue | 🟠 Major

Mask bearer_token in the action schema (SecretHint).

This is sensitive input; add SecretHint so UI masks it and avoids accidental exposure.

🔒 Proposed fix
-from pydantic import BaseModel, HttpUrl, Field
+from pydantic import BaseModel, HttpUrl, Field
+from core.field_hints import SecretHint
@@
 class GetToolsNamesInput(BaseActionInput):
     """Input for MCP tools discovery"""
     sse_endpoint: HttpUrl
     bearer_token: Optional[str] = Field(
         default=None,
-        description="Bearer token for MCP server authentication"
+        description="Bearer token for MCP server authentication",
+        json_schema_extra=SecretHint(reason="API credentials should be masked").to_hints()
     )
🤖 Fix all issues with AI agents
In
`@multi-agent/actions/providers/mcp/validate_connection/validate_connection.py`:
- Around line 7-29: The ValidateConnectionInput model exposes bearer_token
without SecretHint; update the pydantic field for
ValidateConnectionInput.bearer_token to include hint=SecretHint (matching
McpProviderConfig) so the UI treats it as sensitive—locate the
ValidateConnectionInput class and modify the bearer_token Field to pass the
SecretHint hint from core.field_hints.

Comment on lines +7 to +29
import anyio
import time
from typing import Optional, Dict, Any

from pydantic import HttpUrl
from pydantic import HttpUrl, Field

from actions.common.base_action import BaseAction
from actions.common.action_models import BaseActionInput, BaseActionOutput, ActionType
from elements.providers.mcp_server_client.mcp_server_client import McpServerClient
from elements.providers.mcp_server_client.mcp_provider_factory import McpProviderFactory
from elements.providers.mcp_server_client.config import McpProviderConfig
from elements.providers.mcp_server_client.identifiers import Identifier
from core.enums import ResourceCategory
from core.field_hints import SecretHint


# Input/Output models for this action
class ValidateConnectionInput(BaseActionInput):
"""Input for MCP connection validation"""
sse_endpoint: HttpUrl
bearer_token: Optional[str] = Field(
default=None,
description="Bearer token for MCP server authentication"
)
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

Mask bearer_token in the action schema (SecretHint).

The token is sensitive; without SecretHint the UI may render plaintext and risk exposure. Align this with McpProviderConfig by adding SecretHint hints.

🔒 Proposed fix
 class ValidateConnectionInput(BaseActionInput):
     """Input for MCP connection validation"""
     sse_endpoint: HttpUrl
     bearer_token: Optional[str] = Field(
         default=None,
-        description="Bearer token for MCP server authentication"
+        description="Bearer token for MCP server authentication",
+        json_schema_extra=SecretHint(reason="API credentials should be masked").to_hints()
     )
🤖 Prompt for AI Agents
In `@multi-agent/actions/providers/mcp/validate_connection/validate_connection.py`
around lines 7 - 29, The ValidateConnectionInput model exposes bearer_token
without SecretHint; update the pydantic field for
ValidateConnectionInput.bearer_token to include hint=SecretHint (matching
McpProviderConfig) so the UI treats it as sensitive—locate the
ValidateConnectionInput class and modify the bearer_token Field to pass the
SecretHint hint from core.field_hints.

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