== 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
241 lines
14 KiB
Markdown
241 lines
14 KiB
Markdown
#region Tasks [C:3] [TYPE ADR] [SEMANTICS tasks,implementation,agent-chat]
|
||
@defgroup Tasks Implementation tasks for 035-agent-chat-context v2 — Path B model with /agent page, two-layer RBAC, permission_denied flow, and structured truncation.
|
||
|
||
**Input**: Design documents from `/specs/035-agent-chat-context/`
|
||
**Prerequisites**: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/modules.md ✅
|
||
|
||
## Format: `[ID] [P?] [Story] Description`
|
||
|
||
---
|
||
|
||
## Phase 1: Setup
|
||
|
||
- [ ] T001 [P] Materialize API fixtures from `specs/035-agent-chat-context/fixtures/api/` into `backend/tests/fixtures/agent/`
|
||
- [ ] T002 [P] Materialize model fixtures from `specs/035-agent-chat-context/fixtures/model/` into `frontend/src/lib/models/__fixtures__/agent/`
|
||
- [ ] T003 Verify toolchain: `cd backend && source .venv/bin/activate && python -m pytest --co -q`, `cd frontend && npm run test -- --run`
|
||
|
||
---
|
||
|
||
## Phase 2: Foundational
|
||
|
||
- [ ] T004 [P] Define UIContext, ConfirmMetadataV2, PermissionDeniedMetadata, extended ToolCall in `frontend/src/lib/models/AgentChatTypes.ts`
|
||
@POST: UIContext with objectType, objectId, objectName(string|null max 256), envId, route, contextVersion(1)
|
||
@POST: PermissionDeniedMetadata {type, tool_name, required_role, user_role, alternatives}
|
||
@POST: ToolCallStatus extended with "retrying"|"timeout"|"cancelled"; ToolCall +isWriteTool?
|
||
|
||
- [ ] T005 [P] Create `backend/src/agent/_context.py` — UIContext validation
|
||
@POST: validate_uicontext(payload) → validated dict or ValidationError
|
||
@TEST_EDGE: valid→returns, invalid_objectType→422, oversized>4KB→413, objectName>256→422
|
||
|
||
- [ ] T006 Create `backend/src/agent/_tool_filter.py` — tool selection pipeline + invocation guard
|
||
@POST: build_tool_pipeline(tools, user_role, object_type) → filtered list. Order: RBAC→context.
|
||
@POST: enforce_tool_permission(tool_name, user_role) → allowed or yield permission_denied SSE
|
||
@POST: _CONTEXT_TOOL_AFFINITY dict (dashboard 9, dataset 8, migration 5 tools)
|
||
@POST: _TOOL_PERMISSIONS dict (7 admin-only tools)
|
||
@RATIONALE: Ordered pipeline. Two-layer enforcement per AGTL-FR-005.
|
||
@REJECTED: Embedding-first routing — postponed to post-MVP (AGTL-FR-006).
|
||
|
||
---
|
||
|
||
## Phase 3: User Story 1 — Context-Aware Agent (P1)
|
||
|
||
**Goal**: Agent on /agent page receives context from URL params, filters tools by objectType.
|
||
|
||
### Tests for US1
|
||
|
||
- [ ] T007 [P] [US1] L1 model test for setUIContextFromParams + contextPillLabel in `frontend/src/lib/models/__tests__/AgentChatModel.context.test.ts`
|
||
@TEST_FIXTURE: full_params → uiContext populated, pill="📋 dashboard #42 · ss-dev"
|
||
@TEST_FIXTURE: no_params → uiContext null, pill="⚪ Контекст не выбран"
|
||
@TEST_FIXTURE: malformed_objectType → treated as null
|
||
|
||
- [ ] T008 [P] [US1] Contract test for agent_handler uicontext in `backend/tests/test_agent_context.py`
|
||
@TEST_FIXTURE: null→22 tools, dashboard→9 tools, invalid_json→no error+audit log, malformed_objectType→422
|
||
|
||
- [ ] T009 [P] [US1] Contract test for build_tool_pipeline in `backend/tests/test_agent_tool_filter.py`
|
||
@TEST_FIXTURE: null→all RBAC-allowed, dashboard+analyst→dashboard tools minus admin, unknown→graceful fallback
|
||
|
||
### Implementation for US1
|
||
|
||
- [ ] T010 [US1] Implement setUIContextFromParams + contextPillLabel in `frontend/src/lib/models/AgentChatModel.svelte.ts`
|
||
@ACTION: parse URLSearchParams, validate objectType enum, set uiContext with contextVersion=1
|
||
@POST: contextPillLabel derived: icon+type+id+env or "⚪ Контекст не выбран"
|
||
|
||
- [ ] T011 [US1] Implement onMount context init in `frontend/src/routes/agent/+page.svelte`
|
||
@RELATION BINDS_TO -> [AgentChat.Model]
|
||
import { page } from "$app/stores"; onMount: URLSearchParams($page.url.search) → setUIContextFromParams
|
||
|
||
- [ ] T012 [US1] Implement process steps update in `frontend/src/lib/components/agent/AgentChat.svelte`
|
||
First step label uses model.contextPillLabel. Remaining steps unchanged.
|
||
@RELATION BINDS_TO -> [AgentChat.Model]
|
||
|
||
- [ ] T013 [US1] Implement _inject_uicontext in `backend/src/agent/app.py`
|
||
@POST: Appends [USER CONTEXT — informational] block. Explicitly marked "not instructions" for prompt-injection protection.
|
||
|
||
- [ ] T014 [US1] Implement agent_handler uicontext param in `backend/src/agent/app.py`
|
||
@PRE: uicontext_str at position 6 (appended, default None). Validated via validate_uicontext.
|
||
@POST: injected into runtime context. Tools filtered via build_tool_pipeline.
|
||
@SIDE_EFFECT: UIContext logged via logger.reason. Validation failures logged via logger.explore.
|
||
RATIONALE: Appended at end — backward-compat for existing 6-arg Gradio clients.
|
||
|
||
- [ ] T015 [P] [US1] Implement isProdContext + showProdWarning + prod banner in `frontend/src/lib/models/AgentChatModel.svelte.ts` + `AgentChat.svelte`
|
||
isProdContext = $derived((uiContext?.envId?.toLowerCase().includes("prod")) || _activeEnvId?.toLowerCase().includes("prod"))
|
||
Prod banner using `<Icon name="warning">` from $lib/ui (existing). bg-warning-light background.
|
||
@UX_STATE: prod_env→bg-warning-light+banner; non_prod→standard
|
||
|
||
- [ ] T016 [US1] Implement updateActiveEnv in `frontend/src/lib/models/AgentChatModel.svelte.ts`
|
||
@ACTION: syncs _activeEnvId AND uiContext.envId to prevent silent desync
|
||
@POST: both atoms updated. isProdContext $derived recomputes.
|
||
|
||
### Verification for US1
|
||
|
||
- [ ] T017 [US1] Run backend tests: `cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_context.py tests/test_agent_tool_filter.py -v`
|
||
- [ ] T018 [US1] Run frontend tests: `cd frontend && npm run test -- --run frontend/src/lib/models/__tests__/AgentChatModel.context.test.ts`
|
||
- [ ] T019 [US1] E2E: navigate `/dashboards/10?env_id=ss-dev` → click Agent → /agent?objectType=dashboard&objectId=10&envId=ss-dev → pill shows dashboard context
|
||
|
||
---
|
||
|
||
## Phase 4: User Story 2 — Guardrails Refinement (P2)
|
||
|
||
**Goal**: Confirmation card with env badge, risk toning, 10s model-owned countdown, permission_denied as separate SSE event.
|
||
|
||
### Tests for US2
|
||
|
||
- [ ] T020 [P] [US2] Contract test for build_confirmation_contract_v2 + permission denied in `backend/tests/test_agent_confirmation_v2.py`
|
||
@TEST_FIXTURE: deploy_prod→guarded+prod, delete_any→dangerous, read_only→safe
|
||
@TEST_FIXTURE: analyst_deploy→permission_denied SSE (NOT confirm_required)
|
||
@TEST_FIXTURE: env_resolution: tool_args.prod > submit.staging > uiContext.dev
|
||
|
||
- [ ] T021 [P] [US2] L2 UX test for ConfirmationCard 7+1 states in `frontend/src/lib/components/assistant/__tests__/ConfirmationCard.ux.test.ts`
|
||
Verify: read→success, write_staging→warning+badge, write_prod→destructive+PROD, write_dev→info
|
||
Verify: dangerous→destructive+model-owned countdown, permission_denied→muted+lock (separate event)
|
||
|
||
### Implementation for US2
|
||
|
||
- [ ] T022 [US2] Implement build_confirmation_contract_v2 in `backend/src/agent/_confirmation.py`
|
||
@POST: three-axis risk. Env resolution: tool_args.env_id>submit envId>UIContext.envId>null. Env normalization via substring match.
|
||
@DATA_CONTRACT: (tool_name, tool_args, user_role, target_env)→ConfirmMetadataV2
|
||
RATIONALE: Three-axis replaces prefix heuristic; env resolution prevents guardrail misclassification.
|
||
|
||
- [ ] T023 [US2] Implement yield_permission_denied in `backend/src/agent/_confirmation.py`
|
||
@POST: Yields SSE type="permission_denied" (NOT confirm_required). Bypasses HITL checkpoint.
|
||
RATIONALE: Security — forbidden calls must not enter guarded checkpoint.
|
||
|
||
- [ ] T024 [US2] Implement enforce_tool_permission invocation guard in `backend/src/agent/_tool_filter.py`
|
||
@POST: Called before every tool execution. Checks user_role vs _TOOL_PERMISSIONS. Denied→yield permission_denied.
|
||
@RATIONALE: Two-layer enforcement — catches hallucinated/replayed/direct calls bypassing prompt filter.
|
||
|
||
- [ ] T025 [US2] Implement ConfirmationCard env badge + 7 risk tones in `frontend/src/lib/components/assistant/ConfirmationCard.svelte`
|
||
Uses `<Button>` from $lib/ui (existing), `<Icon>` from $lib/ui (existing)
|
||
@UX_STATE: 7 states (read, write_dev, write_staging, write_prod, dangerous, loading_confirm, loading_deny)
|
||
|
||
- [ ] T026 [US2] Implement model-owned dangerous countdown in `frontend/src/lib/models/AgentChatModel.svelte.ts`
|
||
startDangerousCountdown(): setInterval 1s decrement on this.dangerousCountdown. Component reads model atom only.
|
||
@RATIONALE: Single source of truth — eliminates model/component timer race condition.
|
||
|
||
- [ ] T027 [US2] Implement dangerous countdown UI in `frontend/src/lib/components/assistant/ConfirmationCard.svelte`
|
||
Button disabled while model.dangerousCountdown>0. Text: "💀 Удалить (N)". aria-live="assertive" every 1s.
|
||
Reads model.dangerousCountdown — no component timer.
|
||
|
||
- [ ] T028 [US2] Implement permission_denied handler in `frontend/src/lib/models/AgentChat.StreamProcessor.svelte.ts`
|
||
case "permission_denied": render card with 🔒, required_role, alternatives. "Закрыть" only.
|
||
|
||
- [ ] T029 [US2] Implement cancelled tool card for deny flow in `frontend/src/lib/models/AgentChat.StreamProcessor.svelte.ts` + `ToolCallCard.svelte`
|
||
confirm_resolved deny → push ToolCall status="cancelled". Also yield agent message "⏹️ Операция отменена".
|
||
|
||
### Verification for US2
|
||
|
||
- [ ] T030 [US2] Run backend tests: `cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_confirmation_v2.py -v`
|
||
- [ ] T031 [US2] Run frontend tests: `cd frontend && npm run test -- --run frontend/src/lib/components/assistant/__tests__/ConfirmationCard.ux.test.ts`
|
||
- [ ] T032 [US2] Manual: deploy to prod→destructive+PROD badge. delete→10s countdown. analyst→permission_denied (no confirm button)
|
||
|
||
---
|
||
|
||
## Phase 5: User Story 3 — Tool Optimisation (P3)
|
||
|
||
**Goal**: Fixed-delay retry, structured truncation, configurable timeout (write-safe), invocation RBAC guard.
|
||
|
||
### Tests for US3
|
||
|
||
- [ ] T033 [P] [US3] Contract test for _retry_read_tool in `backend/tests/test_agent_retry.py`
|
||
@TEST_FIXTURE: first_502→retries+success, both_502→exhausted, write_tool→no retry
|
||
|
||
- [ ] T034 [P] [US3] Contract test for _summarise_response in `backend/tests/test_agent_summarise.py`
|
||
array 50→"Found 50...+45 more", object→keys+sample, text→sentence-boundary truncation, short→unchanged
|
||
|
||
- [ ] T035 [P] [US3] Contract test for _execute_with_timeout in `backend/tests/test_agent_timeout.py`
|
||
@TEST_EDGE: complete_under→normal, read_exceed→retryable timeout, write_exceed→retryable=false status_unknown
|
||
|
||
- [ ] T036 [P] [US3] L2 UX test for ToolCallCard states in `frontend/src/lib/components/assistant/__tests__/ToolCallCard.ux.test.ts`
|
||
retrying→spinner+attempt, read_timeout→⏱️+retry button, write_timeout→⏱️+"check status" (no retry), cancelled→🚫
|
||
|
||
### Implementation for US3
|
||
|
||
- [ ] T037 [US3] Implement _retry_read_tool in `backend/src/agent/tools.py`
|
||
@PRE: tool risk_level="safe". Error 5xx/ConnectError. Fixed 1s asyncio.sleep.
|
||
@POST: retry once. Yields tool_retry SSE. Exhaust→raises retry_exhausted.
|
||
RATIONALE: Single fixed retry catches ~80% transient failures. Read-only only.
|
||
|
||
- [ ] T038 [US3] Implement _summarise_response in `backend/src/agent/tools.py`
|
||
@POST: >4000 chars + JSON array→"Found N: -item1...+ (N-5) more". Object→keys+sample. Text→sentence-boundary cut.
|
||
RATIONALE: Structured truncation preserves context better than hard cut.
|
||
|
||
- [ ] T039 [US3] Implement _execute_with_timeout in `backend/src/agent/tools.py`
|
||
@PRE: asyncio.wait_for(tool_fn, 30s). is_write param.
|
||
@POST: timeout→tool_timeout SSE. Write: is_write_tool=true, retryable=false.
|
||
RATIONALE: Write tool timeout ≠ mutation didn't happen. No retry — check status instead.
|
||
|
||
- [ ] T040 [US3] Implement tool_retry + tool_timeout handlers in `frontend/src/lib/models/AgentChat.StreamProcessor.svelte.ts`
|
||
case "tool_retry": ToolCall status="retrying". case "tool_timeout": status="timeout", isWriteTool→no retry button.
|
||
|
||
- [ ] T041 [US3] Implement retry+timeout states in `frontend/src/lib/components/assistant/ToolCallCard.svelte`
|
||
retrying: "Повторная попытка... ({N}/{M})". Read timeout: ⏱️+retry btn. Write timeout: ⏱️+"Operation status unknown" (no btn).
|
||
Uses `<Button variant="secondary" size="xs">` from $lib/ui (existing).
|
||
|
||
- [ ] T042 [US3] Implement debug panel context sections in `frontend/src/lib/components/agent/AgentChat.svelte`
|
||
Add "Контекст страницы" (JSON.stringify uiContext) + "Pipeline" (effective tool list after filtering) sections to debug panel.
|
||
Tool list data comes from backend SSE debug event (pipeline_result) — NOT frontend reconstruction.
|
||
|
||
### Verification for US3
|
||
|
||
- [ ] T043 [US3] Run backend tests: `cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_retry.py tests/test_agent_summarise.py tests/test_agent_timeout.py -v`
|
||
- [ ] T044 [US3] Run frontend tests: `cd frontend && npm run test -- --run frontend/src/lib/components/assistant/__tests__/ToolCallCard.ux.test.ts`
|
||
- [ ] T045 [US3] Manual: 502→auto-retry→success. Large→top-5. Analyst→write tools excluded+invocation guard active.
|
||
|
||
---
|
||
|
||
## Phase 6: Polish & Cross-Cutting
|
||
|
||
- [ ] T046 [P] Full backend: `cd backend && source .venv/bin/activate && python -m pytest -v`
|
||
- [ ] T047 [P] Full frontend: `cd frontend && npm run test`
|
||
- [ ] T048 [P] Backend lint: `cd backend && python -m ruff check .`
|
||
- [ ] T049 [P] Frontend lint: `cd frontend && npm run lint`
|
||
- [ ] T050 [P] Frontend build: `cd frontend && npm run build`
|
||
- [ ] T051 Attention compliance audit — verify ATTN_1-4 on all new/changed contracts
|
||
- [ ] T052 E2E verification per quickstart.md
|
||
- [ ] T053 Run quickstart validation
|
||
|
||
---
|
||
|
||
## Dependencies
|
||
|
||
```
|
||
Phase 1 → Phase 2 → US1(P1) → US2(P2) ∥ US3(P3) → Phase 6
|
||
```
|
||
|
||
US2 and US3 can proceed in parallel after US1 base.
|
||
|
||
## Parallel Opportunities
|
||
|
||
Phase 1: T001∥T002 | Phase 2: T004∥T005∥T006 | US1: T007∥T008∥T009, T015∥(T010→T011) | US2: T020∥T021 | US3: T033∥T034∥T035∥T036 | Phase 6: T046∥T047∥T048∥T049∥T050
|
||
|
||
## Notes
|
||
|
||
- Gradio uicontext at position END (6), not middle — backward-compat
|
||
- Permission denied: SSE type="permission_denied", bypasses HITL
|
||
- Countdown: model-owned, component reads only
|
||
- Write timeout: no retry
|
||
- No initialContext in store — context from URL params only
|
||
- RBAC: two-layer (prompt filter + invocation guard)
|
||
|
||
#endregion Tasks
|