Files
ss-tools/specs/035-agent-chat-context/tasks.md
busya ff60865183 feat(agent-chat): 035-agent-chat-context — контекст, guardrails, tools, database discovery
== 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
2026-07-04 22:47:17 +03:00

241 lines
14 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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