Skip to content

Commit 1771277

Browse files
authored
Merge Agentic UI Dashboard with Widget Actions pull request #21 from mjunaidca/008-agentic-ui-dashboard
feat(chatkit): Agentic UI Dashboard with Widget Actions
2 parents c4b6f8b + bbbe925 commit 1771277

44 files changed

Lines changed: 7550 additions & 45 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/agents/engineering/chatkit-expert-agent.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ All patterns are derived from OpenAI's official advanced samples:
183183
4. **Missing widget ID** - `sendCustomAction` needs widget reference
184184
5. **Hardcoding URLs** - Use env vars for API endpoints
185185
6. **Forgetting auth proxy** - httpOnly cookies need server-side access
186+
7. **Not testing widget actions thoroughly** - Claiming completion without testing all buttons/actions with real data
187+
8. **Assuming type hints match runtime** - ChatKit has type annotation vs runtime mismatches (e.g., context parameter)
188+
9. **Using action.arguments** - Action object uses `.payload`, not `.arguments`
189+
10. **Wrapping RequestContext** - Context is already RequestContext, don't wrap it again
190+
11. **Missing Pydantic required fields** - UserMessageItem, Action, etc. have strict validation
191+
12. **Icon-only buttons** - Always add labels to buttons for clarity
192+
13. **No local tool wrappers** - MCP tools alone don't stream widgets (need local wrappers + RunHooks)
193+
14. **Trusting auto-reload** - Python bytecode cache can cause old code to run, manually restart when in doubt
186194

187195
## Self-Monitoring Checklist
188196

@@ -212,6 +220,16 @@ Before completing ChatKit integration:
212220
- [ ] `sendCustomAction` wired for widget updates
213221
- [ ] Entity tagging with search/preview
214222
- [ ] Composer tools if mode switching needed
223+
- [ ] **Action handler uses `action.payload` (NOT `action.arguments`)**
224+
- [ ] **Action context parameter used directly (NOT wrapped in RequestContext)**
225+
- [ ] **UserMessageItem includes all required fields (id, thread_id, created_at, inference_options)**
226+
- [ ] **UserMessageTextContent uses `type="input_text"` for user messages**
227+
- [ ] **Local tool wrappers created for widget-streaming MCP tools**
228+
- [ ] **All widget buttons have clear labels (not just icons)**
229+
- [ ] **Tested all widget actions with real user session**
230+
- [ ] **Verified backend logs show successful action processing**
231+
- [ ] **Checked browser console for validation errors**
232+
- [ ] **Manually restarted server to verify changes (don't trust auto-reload)**
215233

216234
## Skills Used
217235

.claude/agents/engineering/chatkit-integration-agent.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,53 @@ This agent helps integrate OpenAI ChatKit framework with custom backends and AI
195195

196196
**Evidence**: `web-dashboard/src/app/api/chatkit/route.ts` (dedicated proxy)
197197

198+
### Pattern: Type Annotation vs Runtime Mismatch
199+
200+
**What happens**: ValidationError when trying to create RequestContext from what's already a RequestContext
201+
202+
**Why it happens**: Action handler type hint says `context: dict[str, Any]` but ChatKit SDK passes `RequestContext` object at runtime
203+
204+
**How to prevent**: Use `context` directly without wrapping. Type hints are documentation, runtime is reality.
205+
206+
**Evidence**:
207+
```python
208+
# ❌ WRONG
209+
async def action(self, thread, action, sender, context: dict[str, Any]):
210+
request_context = RequestContext(metadata=context) # ValidationError!
211+
212+
# ✅ CORRECT
213+
async def action(self, thread, action, sender, context: dict[str, Any]):
214+
user_id = context.user_id # Use directly, it's already RequestContext
215+
metadata = context.metadata
216+
```
217+
218+
### Pattern: Missing Pydantic Required Fields
219+
220+
**What happens**: Multiple "Field required" ValidationErrors when creating ChatKit objects
221+
222+
**Why it happens**: Pydantic models strictly validate, all required fields must be present
223+
224+
**How to prevent**: Check ChatKit type definitions for required fields. Common culprits:
225+
- UserMessageItem: id, thread_id, created_at, inference_options
226+
- UserMessageTextContent: type="input_text" (not "text")
227+
- Action: payload (not arguments)
228+
229+
**Evidence**: Session debugging showed cascading validation errors until all fields added
230+
231+
### Pattern: Python Auto-Reload Failure
232+
233+
**What happens**: Code changes don't take effect, old code continues running
234+
235+
**Why it happens**: Python bytecode cache (.pyc) or uvicorn reload mechanism lag
236+
237+
**How to prevent**:
238+
1. Use `--reload` flag with uvicorn
239+
2. When in doubt, manually kill process and restart
240+
3. Delete `__pycache__` directories if needed
241+
4. **Never trust auto-reload 100%** - verify changes with logs/breakpoints
242+
243+
**Evidence**: Had to manually restart server multiple times during widget action debugging
244+
198245
## Self-Monitoring Checklist
199246

200247
Before finalizing ChatKit integration:

.claude/skills/engineering/chatkit-actions/SKILL.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,13 +526,213 @@ interface Action {
526526
| @mentions | `entities.onTagSearch` | `ThreadItemConverter` | Reference entities |
527527
| Mode switch | `composer.tools` | Route by tool_choice | Different agents |
528528

529+
## Critical Implementation Details
530+
531+
### Action Object Structure
532+
533+
**IMPORTANT**: The `Action` object uses `payload`, NOT `arguments`:
534+
535+
```python
536+
# ❌ WRONG - Will cause AttributeError
537+
action.arguments # 'Action' object has no attribute 'arguments'
538+
539+
# ✅ CORRECT
540+
action.payload # Access action data via .payload
541+
```
542+
543+
**Action Type Definition**:
544+
```python
545+
from chatkit.types import Action
546+
547+
# Action[str, Any] has these fields:
548+
action.type # str - action identifier (e.g., "task.start")
549+
action.payload # dict[str, Any] - action data
550+
action.handler # "client" | "server" - where action is processed
551+
```
552+
553+
### Server Action Handler Signature
554+
555+
**CRITICAL**: The `context` parameter is `RequestContext`, NOT `dict[str, Any]`
556+
557+
```python
558+
# Type annotation vs runtime reality mismatch
559+
async def action(
560+
self,
561+
thread: ThreadMetadata,
562+
action: Action[str, Any],
563+
sender: WidgetItem | None,
564+
context: dict[str, Any], # ⚠️ Type hint says dict, but runtime is RequestContext!
565+
) -> AsyncIterator[ThreadStreamEvent]:
566+
567+
# ❌ WRONG - Tries to wrap RequestContext inside RequestContext
568+
request_context = RequestContext(metadata=context)
569+
570+
# ✅ CORRECT - Use context directly, it's already RequestContext
571+
user_id = context.user_id
572+
metadata = context.metadata
573+
```
574+
575+
**Why this happens**: ChatKit SDK passes `RequestContext` object at runtime, despite type annotations suggesting `dict`. Always use `context` directly without wrapping.
576+
577+
### UserMessageItem Required Fields
578+
579+
When creating synthetic user messages from actions, **ALL** these fields are required:
580+
581+
```python
582+
from chatkit.types import UserMessageItem, UserMessageTextContent
583+
from datetime import datetime
584+
585+
# ❌ WRONG - Missing required fields causes ValidationError
586+
synthetic_message = UserMessageItem(
587+
content=[UserMessageTextContent(type="text", text=message_text)]
588+
)
589+
590+
# ✅ CORRECT - Include all required fields
591+
synthetic_message = UserMessageItem(
592+
id=self.store.generate_item_id("message", thread, context),
593+
thread_id=thread.id,
594+
created_at=datetime.now(),
595+
content=[UserMessageTextContent(type="input_text", text=message_text)],
596+
inference_options={},
597+
)
598+
```
599+
600+
**Required fields**:
601+
- `id`: Generate via `store.generate_item_id("message", thread, context)`
602+
- `thread_id`: From `thread.id` parameter
603+
- `created_at`: Current timestamp via `datetime.now()`
604+
- `content`: List of content blocks (UserMessageTextContent)
605+
- `inference_options`: Empty dict `{}` if no special options
606+
607+
**UserMessageTextContent type values**:
608+
-`type="input_text"` - User text input (correct)
609+
-`type="text"` - Invalid for UserMessageTextContent (causes ValidationError)
610+
611+
### Local Tool Wrappers for Widget Streaming
612+
613+
**Problem**: Agent calls MCP tool successfully, but widget doesn't appear in UI.
614+
615+
**Root Cause**: Widgets stream via `RunHooks` pattern. MCP tools alone don't trigger widget rendering - you need **local tool wrappers**.
616+
617+
**Solution Pattern**:
618+
619+
```python
620+
# 1. Create local tool wrapper
621+
from agents import function_tool
622+
623+
@function_tool
624+
async def show_task_form(
625+
ctx: RunContextWrapper[TaskFlowAgentContext],
626+
) -> str:
627+
"""Show interactive task creation form widget."""
628+
629+
agent_ctx = ctx.context
630+
mcp_url = agent_ctx.mcp_server_url
631+
632+
# Call MCP tool via HTTP
633+
result = await _call_mcp_tool(
634+
mcp_url,
635+
"taskflow_show_task_form",
636+
arguments={"params": {"user_id": agent_ctx.user_id}},
637+
access_token=agent_ctx.access_token,
638+
)
639+
640+
# Return result - RunHooks will intercept and stream widget
641+
return json.dumps(result)
642+
643+
# 2. Register local wrapper with agent
644+
agent = Agent(
645+
name="TaskFlow Assistant",
646+
tools=[
647+
show_task_form, # Local wrapper - triggers RunHooks
648+
# ... other local wrappers
649+
],
650+
)
651+
652+
# 3. In RunHooks.on_tool_end() - Stream widget
653+
async def on_tool_end(self, output: str | None, tool_name: str) -> None:
654+
if tool_name == "show_task_form":
655+
result = json.loads(output)
656+
if result.get("action") == "show_form":
657+
widget = build_task_form_widget()
658+
yield WidgetItem(...)
659+
```
660+
661+
**Key insight**: Direct MCP tools → no widgets. Local wrappers → RunHooks → widgets streamed.
662+
663+
## Common Pydantic Validation Errors
664+
665+
### Error 1: 'Action' object has no attribute 'arguments'
666+
667+
```
668+
AttributeError: 'Action[str, Any]' object has no attribute 'arguments'
669+
```
670+
671+
**Fix**: Use `action.payload` instead of `action.arguments`
672+
673+
### Error 2: UserMessageTextContent type mismatch
674+
675+
```
676+
ValidationError: Input should be 'input_text' [type=literal_error, input_value='text']
677+
```
678+
679+
**Fix**: Use `type="input_text"` for user input, not `type="text"`
680+
681+
### Error 3: UserMessageItem missing required fields
682+
683+
```
684+
4 validation errors for UserMessageItem
685+
- id: Field required
686+
- thread_id: Field required
687+
- created_at: Field required
688+
- inference_options: Field required
689+
```
690+
691+
**Fix**: Include all required fields when creating UserMessageItem (see pattern above)
692+
693+
### Error 4: RequestContext wrapping issue
694+
695+
```
696+
2 validation errors for RequestContext
697+
user_id: Field required
698+
metadata: Input should be a valid dictionary [input_value=RequestContext(...)]
699+
```
700+
701+
**Fix**: Don't wrap `context` - it's already a RequestContext object
702+
703+
## Widget Action Testing Checklist
704+
705+
Before claiming widget actions are complete, test:
706+
707+
- [ ] Widget renders with correct data
708+
- [ ] All buttons have clear labels (not just icons)
709+
- [ ] Client actions navigate/update UI correctly
710+
- [ ] Server actions call backend successfully
711+
- [ ] Action payload contains all required data
712+
- [ ] Widget updates after server action completes
713+
- [ ] No AttributeError on action.payload access
714+
- [ ] No ValidationError on UserMessageItem creation
715+
- [ ] Local tool wrappers trigger widget streaming
716+
- [ ] All status transitions have appropriate buttons
717+
- [ ] Test with real user session (not mock data)
718+
- [ ] Check browser console for errors
719+
- [ ] Verify backend logs show action processing
720+
- [ ] Test error cases (network failure, invalid data)
721+
529722
## Anti-Patterns to Avoid
530723

531724
1. **Mixing handlers** - Don't handle same action in both client and server
532725
2. **Missing payload** - Always include necessary data in action payload
533726
3. **Forgetting widget ID** - `sendCustomAction` requires widget ID for updates
534727
4. **Not updating widget** - Server actions should yield `ThreadItemReplacedEvent`
535728
5. **Blocking in onAction** - Keep client handlers fast, offload to server
729+
6. **Using action.arguments** - Use `action.payload` (arguments doesn't exist)
730+
7. **Wrapping RequestContext** - Context is already RequestContext, don't wrap it
731+
8. **Missing UserMessageItem fields** - Include id, thread_id, created_at, inference_options
732+
9. **Wrong content type** - Use `type="input_text"` for user messages
733+
10. **No local tool wrappers** - MCP tools alone don't stream widgets
734+
11. **Not testing thoroughly** - Test all actions with real data before claiming done
735+
12. **Assuming type hints are correct** - ChatKit has type annotation vs runtime mismatches
536736

537737
## References
538738

.claude/skills/engineering/chatkit-integration/SKILL.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,24 @@ const { control, sendUserMessage } = useChatKit({
651651
10. **E402 Linter Error**: Imports after load_dotenv()
652652
- **Fix**: Add `# noqa: E402` to intentional imports after dotenv
653653

654+
11. **Type Annotation vs Runtime Mismatch (Action context parameter)**
655+
- **Symptom**: ValidationError trying to create RequestContext from RequestContext
656+
- **Why**: Type hint says `context: dict[str, Any]` but ChatKit SDK passes `RequestContext` object
657+
- **Fix**: Use `context` directly, don't wrap it in RequestContext constructor
658+
- **Detection**: Error message shows `Input should be a valid dictionary [input_value=RequestContext(...)]`
659+
660+
12. **Python Auto-Reload Not Working**: Changes don't take effect
661+
- **Symptom**: Old code still runs despite file changes
662+
- **Why**: Python bytecode cache (.pyc files) or uvicorn reload mechanism lag
663+
- **Fix**: Kill process manually and restart, or delete __pycache__ directories
664+
- **Prevention**: Use `--reload` flag with uvicorn, but be aware it's not 100% reliable
665+
666+
13. **Missing Pydantic Model Required Fields**: ValidationError on ChatKit types
667+
- **Symptom**: "Field required" errors when creating UserMessageItem, Action, etc.
668+
- **Why**: Pydantic models have strict validation, all required fields must be present
669+
- **Fix**: Check ChatKit type definitions, include all required fields with correct types
670+
- **Common mistakes**: Missing id, thread_id, created_at, inference_options fields
671+
654672
## Pattern 6: MCP Agent Authentication (NEW)
655673

656674
**When**: MCP tools need to call authenticated APIs

docker-start.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ fi
109109
echo -e "${YELLOW}[5/6] Running SSO database migrations...${NC}"
110110
cd sso-platform
111111

112+
# Ensure node_modules exists on host for migrations
113+
if [ ! -d "node_modules" ]; then
114+
echo " - Installing dependencies (first time setup)..."
115+
pnpm install --frozen-lockfile
116+
fi
117+
112118
echo " - Pushing SSO schema..."
113119
DATABASE_URL="${DATABASE_URL}" pnpm db:push 2>/dev/null || {
114120
echo -e "${YELLOW} Schema push skipped (may already be up to date)${NC}"

0 commit comments

Comments
 (0)