Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
42d8fd5
Add per-message user feedback that gets sent to langfuse
andybraren Jan 14, 2026
57b54f5
Refactor feedback API to use LangfuseWeb SDK for sending user feedback
andybraren Jan 16, 2026
eea0b7a
Enhance button and checkbox components with cursor pointer style
andybraren Jan 16, 2026
cea2b97
Update feedback components for improved user experience
andybraren Jan 16, 2026
0b32749
Remove truncation from feedback messages
andybraren Jan 16, 2026
b1c7e3e
Update FeedbackModal to enhance privacy notice and improve clarity
andybraren Jan 16, 2026
d760536
Adjust text
andybraren Jan 16, 2026
e2be93d
Refactor feedback API to improve comment structure and metadata handling
andybraren Jan 16, 2026
6d1f225
Enhance feedback API and modal to support optional workflow information
andybraren Jan 16, 2026
0ebdaee
Remove truncation from feedback context and message content in Feedba…
andybraren Jan 16, 2026
8000a7d
Remove LANGFUSE_SECRET_KEY from frontend deployment configuration to …
andybraren Jan 16, 2026
3534578
Remove LANGFUSE_SECRET_KEY from .env.example to enhance security and …
andybraren Jan 16, 2026
cc0abef
Remove secret key from local frontend deployment
andybraren Jan 16, 2026
b49695f
Implement input sanitization in feedback API to prevent log injection
andybraren Jan 16, 2026
5c421ed
Implement user feedback handling via AG-UI META events
andybraren Jan 19, 2026
47f64ad
Fix word
andybraren Jan 19, 2026
327e043
Add input sanitization for feedback
andybraren Jan 19, 2026
7dedfc4
Add error fallback
andybraren Jan 19, 2026
e2b6937
Update error catch route
andybraren Jan 19, 2026
46a0f5c
Add Jira logging command and enhance AG-UI feedback handling
Gkrumbach07 Jan 20, 2026
5cf39c5
Refactor feedback handling to use boolean values
Gkrumbach07 Jan 21, 2026
71b0ae7
Remove session_id from feedback score creation in Langfuse integration
Gkrumbach07 Jan 21, 2026
1c245f7
Merge pull request #9 from Gkrumbach07/pr-509
andybraren Jan 21, 2026
8ca1400
Enhance AG-UI feedback handling and message association
Gkrumbach07 Jan 21, 2026
d358407
Merge pull request #11 from Gkrumbach07/pr-509
andybraren Jan 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions .claude/commands/jira.log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
---
description: Log a new Jira issue to RHOAIENG with Team (Ambient team) and Component (Agentic) pre-filled.
---

## User Input

```text
$ARGUMENTS
```

You **MUST** consider the user input before proceeding (if not empty).

## Goal

Create a new Jira Story in the RHOAIENG project with the correct Team and Component pre-filled for the Ambient team.

## Execution Steps

### 1. Parse User Input

Extract the following from `$ARGUMENTS`:

- **Summary** (required): The title/summary of the issue
- **Description** (optional): Detailed description of the work
- **Issue Type** (optional): Defaults to `Story`, but can be `Bug` or `Task`. Tasks are tech debt related and not user facing
- **Priority** (optional): Defaults to `Normal`

If the user provides a simple sentence, use it as the summary. If they provide multiple lines, use the first line as summary and the rest as description.

### 2. Gather Cold-Start Context

**IMPORTANT**: To make this Jira actionable by an agent, gather the following context. Ask the user for any missing critical info:

**Required for Stories:**
- What is the user-facing goal? (As a [user], I want [X], so that [Y])
- What are the acceptance criteria? (How do we know it's done)
- Which repo/codebase? (e.g., `vTeam`, `ambient-cli`)

**Required for Bugs:**
- Steps to reproduce
- Expected vs actual behaviour
- Environment/browser info if relevant

**Helpful for all types:**
- Relevant file paths or components (e.g., `components/frontend/src/...`)
- Related issues/PRs/design docs
- Screenshots or mockups (as links)
- Constraints or out-of-scope items
- Testing requirements

### 3. Build Structured Description

Format the description using this **agent-friendly template**:

```markdown
## Overview
[One paragraph summary of what needs to be done and why]

## User Story (for Stories)
As a [type of user], I want [goal], so that [benefit].

## Acceptance Criteria
- [ ] [Criterion 1]
- [ ] [Criterion 2]
- [ ] [Criterion 3]

## Technical Context
**Repo**: [repo name or URL]
**Relevant Paths**:
- `path/to/relevant/file.ts`
- `path/to/another/area/`

## Related Links
- Design: [link if any]
- Related Issues: [RHOAIENG-XXXX]
- PR: [link if any]

## Constraints
- [What NOT to do]
- [Boundaries to respect]

## Testing Requirements
- [ ] Unit tests for [X]
- [ ] E2E test for [Y]

## Bug Details (for Bugs only)
**Steps to Reproduce**:
1. Step 1
2. Step 2

**Expected**: [what should happen]
**Actual**: [what actually happens]
**Environment**: [browser/OS if relevant]
```

### 4. Confirm Details

Before creating the issue, confirm with the user:

```
📋 About to create RHOAIENG Jira:

**Summary**: [extracted summary]
**Type**: Story
**Component**: Agentic
**Team**: Ambient team

**Description Preview**:
[Show first 500 chars of formatted description]

This description is structured for agent cold-start. Shall I create this issue? (yes/no/edit)
```

### 5. Create the Jira Issue

Use the `mcp_mcp-atlassian_jira_create_issue` tool with:

```json
{
"project_key": "RHOAIENG",
"summary": "[user provided summary]",
"issue_type": "Story",
"description": "[structured description from template]",
"components": "Agentic"
}
```

Then **update the issue** to set the Team field (must be done as a separate update call):

```json
{
"issue_key": "[CREATED_ISSUE_KEY]",
"fields": {
"customfield_12313240": "6290"
}
}
```

### 6. Report Success

After creation, report:

```
✅ Created: [ISSUE_KEY]
🔗 Link: https://issues.redhat.com/browse/[ISSUE_KEY]

Summary: [summary]
Component: Agentic
Team: Ambient team

📋 Agent Cold-Start Ready: Yes
```

## Examples

### Quick Story (will prompt for more context)

```
/jira.log Add dark mode toggle to session viewer
```

The command will then ask you for acceptance criteria, relevant files, etc.

### Detailed Story (agent-ready)

```
/jira.log Add dark mode toggle to session viewer

As a user, I want to toggle dark mode in the session viewer, so that I can reduce eye strain during long sessions.

Acceptance:
- Toggle persists across sessions (localStorage)
- Respects system preference by default
- Smooth transition animation

Repo: vTeam
Files: components/frontend/src/components/session-viewer/
Related: RHOAIENG-38000 (design system tokens)

Constraints:
- Use existing Shadcn theme tokens, don't create new colours
- Must work with existing syntax highlighting

Tests:
- Unit test for toggle logic
- E2E test for persistence
```

### Bug Report

```
/jira.log [Bug] Session list doesn't refresh after deletion

Steps:
1. Create a session
2. Delete the session via UI
3. Observe the list

Expected: Session disappears from list
Actual: Session remains until page refresh

Repo: vTeam
Files: components/frontend/src/components/session-list/
Browser: Chrome 120, Firefox 121

Fix should invalidate the React Query cache after mutation.
```

### Tech Debt Task

```
/jira.log [Task] Migrate session queries to use React Query v5 patterns

Current queries use deprecated `onSuccess` callbacks.
Need to migrate to the new `select` and mutation patterns.

Repo: vTeam
Files:
- components/frontend/src/services/queries/sessions.ts
- components/frontend/src/hooks/

Constraints:
- Don't change API contracts
- Maintain backwards compatibility with existing components

Tests:
- Existing tests should pass
- Add test for cache invalidation edge case
```

## Field Reference

| Field | Value | Notes |
|-------|-------|-------|
| Project | RHOAIENG | Red Hat OpenShift AI Engineering |
| Component | Agentic | Pre-filled |
| Team | Ambient team | Custom field `customfield_12313240` = `6290` |
| Issue Type | Story | Default, can override with [Bug], [Task] |
| Priority | Normal | Default |

## Agent Cold-Start Checklist

For a Jira to be immediately actionable by an agent, ensure:

| Element | Why It Matters |
|---------|----------------|
| **User Story** | Agent understands the "who" and "why" |
| **Acceptance Criteria** | Clear definition of done, testable outcomes |
| **Repo + File Paths** | Agent knows where to look/edit |
| **Related Links** | Context from design docs, related PRs |
| **Constraints** | Prevents agent from over-engineering or going off-piste |
| **Testing Requirements** | Agent knows what coverage is expected |
| **Bug Repro Steps** | Agent can verify the fix works |

### What Makes a Good vs Bad Jira for Agents

**❌ Bad (vague, agent will struggle):**
> "Fix the login bug"

**✅ Good (agent can start immediately):**
> "Fix login redirect loop on Safari"
>
> **Steps**: 1. Open Safari 2. Click Login 3. Observe infinite redirect
> **Expected**: Redirect to dashboard
> **Actual**: Loops back to login
> **Repo**: vTeam
> **Files**: `components/frontend/src/app/auth/callback/`
> **Constraint**: Don't break Chrome/Firefox flows
> **Test**: Add E2E test for Safari user-agent

## Context

$ARGUMENTS
11 changes: 11 additions & 0 deletions components/backend/handlers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"math"
"regexp"
"time"

authv1 "k8s.io/api/authorization/v1"
Expand All @@ -13,6 +14,16 @@ import (
"k8s.io/client-go/kubernetes"
)

// logSanitizeRegex matches control characters that could enable log injection
// (newlines, carriage returns, null bytes, and other control characters)
var logSanitizeRegex = regexp.MustCompile(`[\x00-\x1F\x7F]`)

// SanitizeForLog removes control characters from a string to prevent log injection attacks.
// This should be used when logging any user-supplied input (headers, query params, form data).
func SanitizeForLog(input string) string {
return logSanitizeRegex.ReplaceAllString(input, "")
}

// GetProjectSettingsResource returns the GroupVersionResource for ProjectSettings
func GetProjectSettingsResource() schema.GroupVersionResource {
return schema.GroupVersionResource{
Expand Down
1 change: 1 addition & 0 deletions components/backend/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func registerRoutes(r *gin.Engine) {
// Runner is a FastAPI server - backend proxies requests and streams SSE responses
projectGroup.POST("/agentic-sessions/:sessionName/agui/run", websocket.HandleAGUIRunProxy)
projectGroup.POST("/agentic-sessions/:sessionName/agui/interrupt", websocket.HandleAGUIInterrupt)
projectGroup.POST("/agentic-sessions/:sessionName/agui/feedback", websocket.HandleAGUIFeedback)
projectGroup.GET("/agentic-sessions/:sessionName/agui/events", websocket.HandleAGUIEvents)
projectGroup.GET("/agentic-sessions/:sessionName/agui/history", websocket.HandleAGUIHistory)
projectGroup.GET("/agentic-sessions/:sessionName/agui/runs", websocket.HandleAGUIRuns)
Expand Down
34 changes: 34 additions & 0 deletions components/backend/types/agui.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const (

// Raw event for pass-through
EventTypeRaw = "RAW"

// META event for user feedback (thumbs up/down)
// See: https://docs.ag-ui.com/drafts/meta-events
EventTypeMeta = "META"
)

// AG-UI Message Roles
Expand Down Expand Up @@ -253,6 +257,36 @@ type RawEvent struct {
Data interface{} `json:"data"`
}

// MetaEvent represents AG-UI META events for user feedback
// See: https://docs.ag-ui.com/drafts/meta-events#user-feedback
type MetaEvent struct {
BaseEvent
MetaType string `json:"metaType"` // "thumbs_up" or "thumbs_down"
Payload map[string]interface{} `json:"payload"`
}

// FeedbackPayload contains the payload for feedback META events
type FeedbackPayload struct {
MessageID string `json:"messageId,omitempty"` // ID of the message being rated
UserID string `json:"userId"` // User providing feedback
Reason string `json:"reason,omitempty"` // Reason for negative feedback
Comment string `json:"comment,omitempty"` // Additional user comment
// Extended fields for Langfuse context
ProjectName string `json:"projectName,omitempty"`
SessionName string `json:"sessionName,omitempty"`
Workflow string `json:"workflow,omitempty"`
Context string `json:"context,omitempty"`
IncludeTranscript bool `json:"includeTranscript,omitempty"`
Transcript []FeedbackTranscriptItem `json:"transcript,omitempty"`
}

// FeedbackTranscriptItem represents a message in the feedback transcript
type FeedbackTranscriptItem struct {
Role string `json:"role"`
Content string `json:"content"`
Timestamp string `json:"timestamp,omitempty"`
}

// NewBaseEvent creates a new BaseEvent with current timestamp
func NewBaseEvent(eventType, threadID, runID string) BaseEvent {
return BaseEvent{
Expand Down
20 changes: 20 additions & 0 deletions components/backend/websocket/agui.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,20 @@ func streamThreadEvents(c *gin.Context, projectName, sessionName string) {
aguiRunsMu.Unlock()

// Filter to only events from COMPLETED runs (have terminal event)
// Also collect session-level META events (feedback, etc.) which may not have runId
completedEvents := make([]map[string]interface{}, 0)
sessionMetaEvents := make([]map[string]interface{}, 0)
skippedCount := 0
for _, event := range events {
eventType, _ := event["type"].(string)
eventRunID, ok := event["runId"].(string)

// META events may not have runId (session-level feedback) - collect them separately
if eventType == types.EventTypeMeta {
sessionMetaEvents = append(sessionMetaEvents, event)
continue
}

if !ok {
continue
}
Expand Down Expand Up @@ -407,6 +417,16 @@ func streamThreadEvents(c *gin.Context, projectName, sessionName string) {
c.Writer.(http.Flusher).Flush()
}
}

// Replay ALL session META events (feedback, tags, annotations)
// META events are session-level and not part of MESSAGES_SNAPSHOT
// They must be replayed regardless of runId to survive reconnects
if len(sessionMetaEvents) > 0 {
for _, event := range sessionMetaEvents {
writeSSEEvent(c.Writer, event)
}
c.Writer.(http.Flusher).Flush()
}
} else if err != nil {
log.Printf("AGUI: Failed to load events: %v", err)
}
Expand Down
Loading
Loading