== User stories == US1: Контекст с дашборда/датасета → /agent с URL params US2: Guardrails card — env badge, 7 risk tones, countdown, permission_denied US3: Tools optimization — retry, timeout, summarise, RBAC + context affinity == Backend == - _context.py (NEW): UIContext validation (7 checks) - _tool_filter.py (NEW): RBAC + context affinity pipeline - _confirmation.py: build_confirmation_contract_v2, permission_denied_payload - tools.py: superset_list_databases, retry/summarise/timeout wrappers - app.py: _inject_uicontext, _inject_env_id_into_tools, database prefetch в runtime context - _persistence.py: prefetch_databases() - agent_superset_explore.py: GET /databases endpoint - _llm_async_http.py, _persistence.py: fix double /v1 в LLM URL (LM Studio Unexpected endpoint) == Frontend == - AgentChatModel.svelte.ts: 5 atoms, 3 actions, countdown, context - AgentChat.svelte: production banner, process steps, debug panel - ConfirmationCard.svelte: 7 risk tones, permission_denied, countdown - ToolCallCard.svelte: retrying/timeout/cancelled states - StreamProcessor.svelte.ts: tool_retry, timeout, permission_denied - TopNavbar: sparkles icon + Ассистент - sidebarNavigation: AI section - DashboardHeader, datasets/+page: contextual AI buttons - Icon: sparkles, brain, cpu icons - tailwind: assistant category colors - i18n: en/ru nav keys == Tests == - 159 backend agent tests (+16 US3: retry, timeout, summarise, contracts) - 2544 frontend tests (+11 model + component tests) - 15 JSON fixtures (10 API + 5 model) == Specs == - specs/035-agent-chat-context/: spec, UX, plan, tasks, research, data-model, contracts, quickstart, traceability, fixtures, checklists Closes #035
136 lines
4.3 KiB
Markdown
136 lines
4.3 KiB
Markdown
#region DataModel [C:3] [TYPE ADR] [SEMANTICS data-model,agent-chat,schema]
|
||
@defgroup DataModel Data structures for 035-agent-chat-context — frontend TypeScript DTOs and backend Python counterparts.
|
||
@RATIONALE Updated for Path B (/agent page), Gradio payload safety, two-layer RBAC, and permission_denied event type.
|
||
|
||
## 1. UIContext — Navigation Context Payload
|
||
|
||
### Source
|
||
Derived from URL query parameters on `/agent` page entry. Static for visit duration. EnvId syncable via `updateActiveEnv`.
|
||
|
||
### TypeScript (`frontend/src/lib/models/AgentChatTypes.ts`)
|
||
|
||
```typescript
|
||
export interface UIContext {
|
||
objectType: "dashboard" | "dataset" | "migration" | null;
|
||
objectId: string | null;
|
||
objectName: string | null; // max 256 chars
|
||
envId: string | null; // synced with EnvSelector via updateActiveEnv
|
||
route: string; // source page route (e.g. "/dashboards/42")
|
||
contextVersion: number; // currently 1
|
||
}
|
||
```
|
||
|
||
### Python validation (`backend/src/agent/_context.py` — NEW)
|
||
|
||
```python
|
||
# UIContext validation rules:
|
||
# objectType: must be one of ("dashboard","dataset","migration",None)
|
||
# objectId: must be numeric string or None
|
||
# objectName: max 256 chars
|
||
# envId: string or None
|
||
# route: string, max 512 chars
|
||
# contextVersion: positive integer ≤ 1
|
||
# Max payload size: 4 KB (413 Payload Too Large if exceeded)
|
||
```
|
||
|
||
---
|
||
|
||
## 2. ConfirmMetadataV2 — SSE confirm_required Event
|
||
|
||
```json
|
||
{
|
||
"metadata": {
|
||
"type": "confirm_required",
|
||
"thread_id": "uuid",
|
||
"tool_name": "deploy_dashboard",
|
||
"tool_args": {"env_id": "prod", "dashboard_id": 42},
|
||
"risk": "write",
|
||
"risk_level": "guarded",
|
||
"dangerous": false,
|
||
"env_context": "prod",
|
||
"prompt": "⚠️ Изменение данных в production",
|
||
"requires_confirmation": true
|
||
}
|
||
}
|
||
```
|
||
|
||
**Env resolution rule** (canonical):
|
||
1. `tool_args.env_id` (tool-targeted environment)
|
||
2. Submit-time `envId` parameter (position 5 in Gradio args)
|
||
3. `UIContext.envId` (from URL params)
|
||
4. `null` (no environment)
|
||
|
||
**Env normalization**: `env_id` containing "prod"/"production" → `"prod"`; "stag"/"test" → `"staging"`; "dev"/"local" → `"dev"`. Unknown values → `null`.
|
||
|
||
---
|
||
|
||
## 3. PermissionDeniedMetadata — SSE permission_denied Event (NEW)
|
||
|
||
```json
|
||
{
|
||
"metadata": {
|
||
"type": "permission_denied",
|
||
"tool_name": "deploy_dashboard",
|
||
"required_role": "admin",
|
||
"user_role": "analyst",
|
||
"alternatives": [
|
||
{"action": "get_health_summary", "prompt": "Запросить отчёт о состоянии дашборда"}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**Distinct from confirm_required**: This event BYPASSES HITL checkpoint. Frontend renders permission-denied card with «Закрыть» button — no confirm. Emitted at invocation-time guard (AgentChat.ToolFilter.InvocationGuard).
|
||
|
||
---
|
||
|
||
## 4. New SSE Event Types
|
||
|
||
| Event | When | Fields |
|
||
|-------|------|--------|
|
||
| `permission_denied` | Tool invocation rejected by RBAC guard | tool_name, required_role, user_role, alternatives |
|
||
| `tool_retry` | Read-only tool auto-retrying | tool, attempt, max_attempts |
|
||
| `tool_timeout` | Tool exceeded 30s timeout | tool, timeout_seconds, is_write_tool, retryable |
|
||
|
||
### `tool_timeout` (extended)
|
||
|
||
```json
|
||
{"metadata":{"type":"tool_timeout","tool":"superset_explore_database","timeout_seconds":30,"is_write_tool":false,"retryable":true}}
|
||
```
|
||
|
||
For write tools (`is_write_tool=true`): `retryable=false`, frontend shows "Operation status unknown — check Superset" (no retry button).
|
||
|
||
---
|
||
|
||
## 5. AgentChatModel — New Atoms (v2)
|
||
|
||
```typescript
|
||
// ADD to AgentChatModel class:
|
||
uiContext: UIContext | null = $state(null);
|
||
dangerousCountdown: number = $state(0);
|
||
dangerousCountdownActive: boolean = $state(false);
|
||
_activeEnvId: string | null = $state(null);
|
||
permissionAlternatives: Array<{ action: string; prompt: string }> | null = $state(null);
|
||
|
||
// ADD derived:
|
||
isProdContext: boolean = $derived(...);
|
||
showProdWarning: boolean = $derived(...);
|
||
contextPillLabel: string = $derived(...);
|
||
|
||
// updateActiveEnv now syncs BOTH atoms:
|
||
updateActiveEnv(envId: string | null): void {
|
||
this._activeEnvId = envId;
|
||
if (this.uiContext) this.uiContext.envId = envId;
|
||
}
|
||
```
|
||
|
||
**REMOVED**: `toolAffinity` atom (debug data must come from backend SSE, not frontend reconstruction).
|
||
|
||
---
|
||
|
||
## 6. Existing DB Models — No Changes
|
||
|
||
No new tables, no schema migrations. Context not persisted in conversations.
|
||
|
||
#endregion DataModel
|