== 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
342 lines
21 KiB
Markdown
342 lines
21 KiB
Markdown
#region ModulesContract [C:4] [TYPE ADR] [SEMANTICS contracts,modules,agent-chat]
|
||
@defgroup Contracts Module and function contracts for 035-agent-chat-context implementation.
|
||
@RATIONALE Content model updated for Path B (/agent dedicated page), Gradio payload safety, two-layer RBAC enforcement, and permission_denied flow separation.
|
||
@REJECTED Path A (drawer panel on any page) — rejected in favor of dedicated /agent route for simpler state management.
|
||
|
||
#region AgentChat.LangGraph.Handler [C:4] [TYPE Function] [SEMANTICS agent-chat,handler,context]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Extended agent_handler — accepts uicontext JSON appended at end of positional args, injects into runtime context, filters tools by objectType.
|
||
# @PRE JWT valid, user authenticated. uicontext_str is valid JSON or None.
|
||
# @POST UIContext injected into system prompt. Tools filtered by objectType. Stream unchanged for uicontext=None (backward-compat).
|
||
# @SIDE_EFFECT Calls LLM, invokes tools, writes checkpoints. UIContext validated and logged via CoT logger.
|
||
# @RELATION DEPENDS_ON -> [AgentChat.ToolFilter]
|
||
# @RELATION DEPENDS_ON -> [AgentChat.Confirmation]
|
||
# @DATA_CONTRACT Input: (message, history, convId, userId, userJwt, envId, uicontext_str?) -> Output: AsyncGenerator[str]
|
||
# @TEST_EDGE: null_uicontext -> All 22 tools passed to LLM (backward-compat).
|
||
# @TEST_EDGE: dashboard_context -> Only 9 dashboard tools passed.
|
||
# @TEST_EDGE: invalid_json_uicontext -> Gracefully ignored with audit log, all tools passed.
|
||
# @TEST_EDGE: malformed_objectType -> Rejected with 422, audit log written.
|
||
# @RATIONALE uicontext_str appended at END of positional args (position 6) — inserting in the middle would break existing Gradio clients that pass 6 args. Null default ensures zero breakage.
|
||
# @REJECTED Middle-position insertion — rejected: breaking change for existing Gradio clients.
|
||
#endregion AgentChat.LangGraph.Handler
|
||
|
||
#region AgentChat.ToolFilter [C:3] [TYPE Module] [SEMANTICS agent-chat,tools,filter,context]
|
||
# @defgroup AgentChat Context-aware tool filtering by objectType.
|
||
# @LAYER Service
|
||
# @RELATION DEPENDS_ON -> [AgentChat.Tools]
|
||
|
||
# #region AgentChat.ToolFilter.Pipeline [C:4] [TYPE Function] [SEMANTICS agent-chat,tools,filter,pipeline]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Canonical tool selection pipeline — applies in order: RBAC filter → context affinity filter.
|
||
# @POST Returns list[BaseTool] after applying: (1) role-based exclusion, (2) context-type hard filter. show_capabilities always included.
|
||
# @SIDE_EFFECT CoT logs reason for each excluded tool.
|
||
# @RELATION DEPENDS_ON -> [AgentChat.Tools]
|
||
# @DATA_CONTRACT Input: (all_tools, user_role, object_type) -> Output: filtered_tools
|
||
# @RATIONALE Ordered pipeline ensures RBAC is enforced FIRST (security), then context filtering (UX). Two-layer: prompt filtering + invocation enforcement (see AGTL-FR-005).
|
||
# @REJECTED Embedding-based filtering — rejected for initial release; postponed to post-MVP (AGTL-FR-006).
|
||
# @TEST_EDGE: null_objectType -> Returns all RBAC-allowed tools.
|
||
# @TEST_EDGE: dashboard+analyst -> Returns 9 dashboard tools minus any requiring admin.
|
||
# @TEST_EDGE: unknown_objectType -> Returns all RBAC-allowed tools (graceful fallback).
|
||
|
||
_CONTEXT_TOOL_AFFINITY: dict[str, set[str]] = {
|
||
"dashboard": {"search_dashboards", "get_health_summary", "deploy_dashboard",
|
||
"run_llm_validation", "run_llm_documentation", "execute_migration",
|
||
"create_branch", "commit_changes"},
|
||
"dataset": {"superset_explore_database", "superset_format_sql", "superset_audit_permissions",
|
||
"superset_execute_sql", "superset_create_dataset", "search_dashboards",
|
||
"get_task_status", "list_environments"},
|
||
"migration": {"execute_migration", "search_dashboards", "get_health_summary",
|
||
"deploy_dashboard", "list_environments"},
|
||
}
|
||
|
||
_TOOL_PERMISSIONS: dict[str, list[str]] = {
|
||
"deploy_dashboard": ["admin"], "commit_changes": ["admin"],
|
||
"create_branch": ["admin"], "run_backup": ["admin"],
|
||
"execute_migration": ["admin"], "start_maintenance": ["admin"],
|
||
"end_maintenance": ["admin"],
|
||
}
|
||
|
||
def build_tool_pipeline(tools, user_role, object_type):
|
||
"""Apply RBAC + context filtering in canonical order."""
|
||
pass
|
||
# #endregion AgentChat.ToolFilter.Pipeline
|
||
|
||
# #region AgentChat.ToolFilter.InvocationGuard [C:3] [TYPE Function] [SEMANTICS agent-chat,tools,security,guard]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Invocation-time RBAC enforcement — validates tool execution permission regardless of prompt filtering.
|
||
# @PRE tool_name extracted from tool invocation request. User role resolved from JWT.
|
||
# @POST If role insufficient: yield permission_denied SSE event (NOT confirm_required). If role sufficient: proceed.
|
||
# @SIDE_EFFECT CoT logger.explore on denied. Audit log written.
|
||
# @DATA_CONTRACT Input: (tool_name, user_role) -> Output: None (allowed) or yield permission_denied SSE
|
||
# @RATIONALE Two-layer enforcement (AGTL-FR-005): prompt filtering prevents LLM from proposing disallowed tools, but invocation guard catches hallucinated/replayed/direct calls. Security-critical — prompt filtering alone is insufficient.
|
||
# @TEST_EDGE: analyst_calls_deploy -> permission_denied SSE, tool not executed.
|
||
# @TEST_EDGE: admin_calls_deploy -> allowed, no event.
|
||
# @TEST_EDGE: unknown_tool -> rejected with error, not permission_denied.
|
||
|
||
def enforce_tool_permission(tool_name, user_role):
|
||
"""Invocation-time RBAC enforcement — yield permission_denied or proceed."""
|
||
pass
|
||
# #endregion AgentChat.ToolFilter.InvocationGuard
|
||
|
||
#endregion AgentChat.ToolFilter
|
||
|
||
#region AgentChat.Tools.Retry [C:3] [TYPE Function] [SEMANTICS agent-chat,tools,retry,http]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Fixed-delay retry wrapper for read-only tool HTTP calls on transient errors.
|
||
# @PRE Tool is classified as read-only (risk_level="safe"). Error is 5xx or connection error.
|
||
# @POST Retries once with fixed 1s delay. On success: returns response. On exhaust: raises with retry_exhausted=True.
|
||
# @SIDE_EFFECT HTTP call retried; CoT loggers REASON/EXPLORE/REFLECT on each attempt.
|
||
# @RELATION DEPENDS_ON -> [EXT:httpx:AsyncClient]
|
||
# @TEST_EDGE: first_attempt_502 -> Auto-retries once, succeeds.
|
||
# @TEST_EDGE: both_attempts_502 -> Raises error with retry_exhausted=True.
|
||
# @TEST_EDGE: write_tool_502 -> No retry (read-only only), raises immediately.
|
||
# @RATIONALE 1 retry with 1s fixed delay catches ~80% of transient failures. Read-only only — write idempotency not guaranteed.
|
||
# @REJECTED Exponential backoff — rejected: single retry cannot be exponential; 2+ retries with backoff adds 3s+ latency.
|
||
# @REJECTED Retry all tools — rejected: write operations may not be idempotent.
|
||
|
||
async def _retry_read_tool(func, *args, **kwargs):
|
||
"""Auto-retry read-only tool on transient HTTP errors. Fixed 1s delay."""
|
||
pass
|
||
# #endregion AgentChat.Tools.Retry
|
||
|
||
#region AgentChat.Tools.Summarise [C:2] [TYPE Function] [SEMANTICS agent-chat,tools,summarise,response]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Structured response summarisation — top-N items + total count for large tool responses.
|
||
# @POST When response >4000 chars and JSON array: "Found {total} items:\n - {item1}\n ...\n + {total-5} more."
|
||
# When >4000 chars and JSON object: "Result keys: {keys}. Values: {sample}..."
|
||
# When >4000 chars and not JSON: truncate at sentence boundary near 4000 chars + "..."
|
||
# When ≤4000 chars: return original.
|
||
# @RATIONALE Structured truncation preserves semantic value for LLM context better than hard cut.
|
||
# @REJECTED LLM-based summarisation — extra token cost and latency.
|
||
# @REJECTED Pagination in every tool — 22 tools to modify. Terminology: "structured truncation" not "semantic summarisation."
|
||
|
||
def _summarise_response(text: str, limit: int = 4000) -> str:
|
||
"""Structured truncation — top-N + count for large responses."""
|
||
pass
|
||
# #endregion AgentChat.Tools.Summarise
|
||
|
||
#region AgentChat.Tools.Timeout [C:3] [TYPE Function] [SEMANTICS agent-chat,tools,timeout]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Configurable timeout wrapper for tool execution (default 30s).
|
||
# @PRE asyncio event loop running.
|
||
# @POST Returns tool result within timeout. On timeout: yields tool_timeout SSE. For write tools: message says "operation status unknown — check status" (NOT "retry").
|
||
# @SIDE_EFFECT asyncio.wait_for with timeout; CoT logger.explore on timeout.
|
||
# @RELATION DEPENDS_ON -> [EXT:asyncio]
|
||
# @TEST_EDGE: tool_completes_under_timeout -> Returns result normally.
|
||
# @TEST_EDGE: read_tool_exceeds_30s -> Yields tool_timeout with retryable=true.
|
||
# @TEST_EDGE: write_tool_exceeds_30s -> Yields tool_timeout with retryable=false, status_unknown=true.
|
||
# @RATIONALE 30s default balances responsiveness with Superset API variability. Write-tool timeout is NOT safe to retry — the mutation may have succeeded server-side.
|
||
# @REJECTED Auto-retry on write timeout — rejected: unsafe, may duplicate mutations.
|
||
|
||
async def _execute_with_timeout(tool_fn, is_write: bool = False, timeout_s: int = 30):
|
||
"""Execute tool with configurable timeout. Write tools: no auto-retry on timeout."""
|
||
pass
|
||
# #endregion AgentChat.Tools.Timeout
|
||
|
||
#region AgentChat.Confirmation.GuardV2 [C:4] [TYPE Function] [SEMANTICS agent-chat,hitl,confirmation,guardrails]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Build extended confirmation contract — three-axis risk classification with env context.
|
||
# @POST Returns dict with risk, risk_level, dangerous, env_context, permission_granted, required_role, alternatives.
|
||
# @RELATION DEPENDS_ON -> [AgentChat.ToolResolver]
|
||
# @RELATION DEPENDS_ON -> [EXT:FastAPI:RBAC]
|
||
# @DATA_CONTRACT Input: (tool_name, tool_args, user_role, target_env) -> Output: ConfirmMetadataV2
|
||
# @RATIONALE Three-axis classification (tool_risk × env_risk × permission) replaces single-axis prefix heuristic.
|
||
# Env resolution: tool_args.env_id > submit envId > UIContext.envId > null.
|
||
# Env normalization for risk tiers: env_id containing "prod" → "prod"; "stag"/"test" → "staging"; "dev"/"local" → "dev".
|
||
# @REJECTED Single-axis prefix-only — cannot distinguish deploy to prod vs staging.
|
||
# @REJECTED LLM-based risk scoring — adds 500ms latency.
|
||
# @TEST_EDGE: deploy_to_prod -> risk="write", risk_level="guarded", env_context="prod", dangerous=false.
|
||
# @TEST_EDGE: delete_dashboard_any_env -> risk_level="dangerous", dangerous=true.
|
||
# @TEST_EDGE: search_dashboards -> risk="read", risk_level="safe", dangerous=false.
|
||
# @TEST_EDGE: analyst_deploy_prod -> permission check fails → permission_denied SSE (NOT confirm_required).
|
||
|
||
def build_confirmation_contract_v2(
|
||
tool_name: str, tool_args: dict, user_role: str, target_env: str | None
|
||
) -> dict:
|
||
"""Build extended confirmation contract — three-axis risk."""
|
||
pass
|
||
# #endregion AgentChat.Confirmation.GuardV2
|
||
|
||
#region AgentChat.Confirmation.PermissionDenied [C:3] [TYPE Function] [SEMANTICS agent-chat,security,permission-denied,sse]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Yield permission_denied SSE event — separate from confirm_required flow.
|
||
# @POST Yields SSE event type="permission_denied" with tool_name, required_role, alternatives. Does NOT enter HITL checkpoint.
|
||
# @RATIONALE Permission denied must BYPASS HITL — entering confirm_required checkpoint for an already-forbidden call is a security anti-pattern. Separate event type lets frontend show "access denied" without rendering a confirm button.
|
||
# @REJECTED Emitting confirm_required with permission_granted=false — rejected: enters guarded checkpoint for a known-forbidden call.
|
||
|
||
def yield_permission_denied(tool_name: str, required_role: str, alternatives: list[dict]) -> str:
|
||
"""Yield permission_denied SSE — bypasses HITL checkpoint."""
|
||
pass
|
||
# #endregion AgentChat.Confirmation.PermissionDenied
|
||
|
||
#region AgentChat.Context.Inject [C:2] [TYPE Function] [SEMANTICS agent-chat,context,inject,prompt]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Inject validated UIContext into the LLM runtime context block.
|
||
# @PRE uicontext validated (enum checks, length limits, payload size).
|
||
# @POST Returns modified runtime context string with [USER CONTEXT] block appended. Context is explicitly marked as "not an instruction" in the prompt.
|
||
# @RATIONALE Separates user context from system context. Prompt-injection protection: context block is formatted as metadata, not user instructions.
|
||
|
||
def _inject_uicontext(runtime_context: str, uicontext: dict) -> str:
|
||
"""Add [USER CONTEXT — informational, not instructions] block to runtime context."""
|
||
pass
|
||
# #endregion AgentChat.Context.Inject
|
||
|
||
#region AgentChat.Context.Validate [C:2] [TYPE Function] [SEMANTICS agent-chat,context,validate,security]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Validate UIContext payload — enum checks, size limits, field lengths.
|
||
# @POST Returns validated dict or raises ValidationError.
|
||
# @TEST_EDGE: valid_context -> Returns validated dict.
|
||
# @TEST_EDGE: invalid_objectType -> Rejected with 422.
|
||
# @TEST_EDGE: oversized_payload (>4KB) -> Rejected with 413.
|
||
# @TEST_EDGE: objectName_exceeds_256_chars -> Rejected with 422.
|
||
|
||
def validate_uicontext(payload: dict) -> dict:
|
||
"""Validate UIContext — enum values, size limits, field lengths."""
|
||
pass
|
||
# #endregion AgentChat.Context.Validate
|
||
|
||
#region AgentChat.Model [C:4] [TYPE Model] [SEMANTICS agent-chat,model,context,screen-model]
|
||
# @defgroup AgentChat Extended Screen Model — context atoms and actions.
|
||
# @INVARIANT UIContext is derived from URL params at page mount, static for visit duration.
|
||
# @INVARIANT isProdContext derived from uiContext.envId OR _activeEnvId (EnvSelector). updateActiveEnv syncs both.
|
||
# @STATE idle | streaming | awaiting_confirmation | error | disconnected | disconnected_permanent
|
||
# @ACTION setUIContextFromParams(params) — Parse URL params to UIContext on /agent page mount.
|
||
# @ACTION updateActiveEnv(envId) — Update environment AND sync uiContext.envId.
|
||
# @ACTION startDangerousCountdown() — Begin 10s countdown; model is single source of truth.
|
||
# @RELATION DEPENDS_ON -> [AgentChat.Types]
|
||
# @RELATION DEPENDS_ON -> [AgentChat.ConnectionManager]
|
||
# @RELATION DEPENDS_ON -> [AgentChat.StreamProcessor]
|
||
# @RATIONALE Existing AgentChatModel extended rather than replaced — atom addition is additive.
|
||
# @REJECTED New context-only model — rejected: cross-model coordination for streaming state.
|
||
# @REJECTED initialContext in assistantChatStore — rejected: violates context-per-visit model; store persistence creates stale-context trap.
|
||
|
||
// NEW atoms (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);
|
||
|
||
// NEW actions:
|
||
// setUIContextFromParams(params: URLSearchParams): void { ... }
|
||
// updateActiveEnv(envId: string | null): void { this._activeEnvId = envId; if (this.uiContext) this.uiContext.envId = envId; }
|
||
// startDangerousCountdown(): void { ... }
|
||
// #endregion AgentChat.Model
|
||
|
||
# #region AgentChat.Model.SetContext [C:3] [TYPE Function] [SEMANTICS agent-chat,model,context,action]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Parse URL search params into UIContext atom on /agent page mount.
|
||
# @ACTION Public action — called from +page.svelte onMount.
|
||
# @POST this.uiContext populated. this._activeEnvId set. CoT log emitted.
|
||
# @TEST_EDGE: full_params -> uiContext populated with all fields, contextVersion=1.
|
||
# @TEST_EDGE: no_params -> uiContext is null, pill shows "Контекст не выбран".
|
||
# @TEST_EDGE: malformed_objectType -> treated as null (frontend validation).
|
||
|
||
// setUIContextFromParams(params: URLSearchParams): void {
|
||
// const objectType = (["dashboard","dataset","migration"].includes(params.get("objectType")||"")
|
||
// ? params.get("objectType") : null) as UIContext["objectType"];
|
||
// ...
|
||
// }
|
||
# #endregion AgentChat.Model.SetContext
|
||
|
||
#region AgentChat.Component [C:4] [TYPE Component] [SEMANTICS agent,chat,ui,svelte]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Agent chat panel — extended with prod warning background+banner and context-aware process steps.
|
||
# @RELATION BINDS_TO -> [AgentChat.Model]
|
||
# @RELATION DEPENDS_ON -> [$lib/ui/Icon]
|
||
# @UX_STATE prod_env -> bg-warning-light background + red PRODUCTION banner atop chat panel.
|
||
# @UX_STATE non_prod_env -> Standard bg-surface-page background, no banner.
|
||
# @UX_FEEDBACK Prod banner appears/disappears reactively on env change.
|
||
# @UX_RECOVERY EnvSelector → select non-prod → background restores.
|
||
# @RATIONALE Reactive background change gives immediate, non-intrusive prod warning.
|
||
# @REJECTED Modal warning on every prod message — too intrusive.
|
||
|
||
// CHANGES to AgentChat.svelte:
|
||
// 1. Root div: class binding for prod background
|
||
// 2. Prod banner using $lib/ui/Icon (existing)
|
||
// 3. Process steps: first step uses model.contextPillLabel
|
||
#endregion AgentChat.Component
|
||
|
||
#region AgentChat.ConfirmationCard [C:4] [TYPE Component] [SEMANTICS agent-chat,confirmation,hitl,guardrails,svelte]
|
||
# @ingroup AgentChat
|
||
# @BRIEF HITL guardrails card — env badge, risk toning, dangerous countdown, permission-denied state.
|
||
# @RELATION BINDS_TO -> [AgentChat.Model]
|
||
# @RELATION DEPENDS_ON -> [$lib/ui/Button]
|
||
# @RELATION DEPENDS_ON -> [$lib/ui/Icon]
|
||
# @UX_STATE read/write_staging/write_prod/write_dev/dangerous/loading_confirm/loading_deny — 7 states.
|
||
# @UX_STATE permission_denied — Separate rendering path; received as SSE event type="permission_denied" (NOT confirm_required).
|
||
# @UX_FEEDBACK Dangerous countdown aria-live="assertive" every 1s (aligned with 1s decrement).
|
||
# @UX_RECOVERY Deny → "Операция отменена" agent message in chat.
|
||
# @UX_RECOVERY Permission denied → close card → agent message with alternatives.
|
||
# @INVARIANT Card renders from confirm_required for permitted operations. permission_denied events bypass HITL checkpoint.
|
||
# @INVARIANT Countdown timer owned by model (dangerousCountdown atom), component reads model only.
|
||
# @RATIONALE Model-owns-countdown eliminates race condition between model and component timers.
|
||
# @REJECTED Component-owned countdown — rejected: violates model-first pattern, creates timer duplication.
|
||
|
||
// CHANGES to ConfirmationCard.svelte:
|
||
// 1. 7 risk-based states (add write_dev)
|
||
// 2. Countdown reads model.dangerousCountdown (no component timer)
|
||
// 3. permission_denied: listens for SSE event type="permission_denied"
|
||
// 4. Risk-based toneClasses extended with write_dev
|
||
#endregion AgentChat.ConfirmationCard
|
||
|
||
#region AgentChat.ToolCallCard [C:3] [TYPE Component] [SEMANTICS agent-chat,tools,retry,timeout,svelte]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Inline tool call display — retrying, timeout, cancelled states.
|
||
# @RELATION DEPENDS_ON -> [$lib/ui/Button]
|
||
# @UX_STATE retrying/timeout/cancelled
|
||
# @UX_FEEDBACK Retry button triggers message resubmission for read-only tools. Write tool timeout: no retry button ("check operation status").
|
||
# @UX_RECOVERY Read timeout → retry. Write timeout → check status.
|
||
# @RATIONALE Write-tool retry is unsafe — timeout does not mean mutation didn't happen.
|
||
# @REJECTED Retry for all timeouts — rejected: write retry may duplicate mutations.
|
||
|
||
// CHANGES to ToolCallCard.svelte:
|
||
// 1. onRetry prop shown only for read tools / transient errors
|
||
// 2. Write timeout: "⏱️ Operation status unknown — check Superset" (no retry button)
|
||
#endregion AgentChat.ToolCallCard
|
||
|
||
#region AgentChat.Page [C:3] [TYPE Component] [SEMANTICS agent-chat,page,svelte]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Agent page wrapper — reads UIContext from URL params on mount.
|
||
# @RELATION BINDS_TO -> [AgentChat.Model]
|
||
# @UX_STATE mount -> Reads URL params, calls model.setUIContextFromParams().
|
||
|
||
// CHANGES to +page.svelte:
|
||
// import { page } from "$app/stores";
|
||
// onMount(() => {
|
||
// const params = new URLSearchParams($page.url.search);
|
||
// model.setUIContextFromParams(params);
|
||
// });
|
||
#endregion AgentChat.Page
|
||
|
||
#region AgentChat.StreamProcessor [C:4] [TYPE Module] [SEMANTICS agent-chat,streaming,sse,svelte]
|
||
# @ingroup AgentChat
|
||
# @BRIEF SSE stream processor — handles tool_retry, tool_timeout, permission_denied (new), extended confirm_required.
|
||
# @RELATION BINDS_TO -> [AgentChat.Model]
|
||
# @RELATION DEPENDS_ON -> [AgentChat.Types]
|
||
|
||
// NEW event handler:
|
||
// case "permission_denied": render permission-denied card (separate from confirm_required)
|
||
|
||
// EXTENDED event handler:
|
||
// case "confirm_required": parse dangerous, env_context, permission_granted. If permission_granted=false → redirect to permission_denied handler.
|
||
|
||
// NEW handlers:
|
||
// case "tool_retry": update ToolCall status=retrying
|
||
// case "tool_timeout": update ToolCall status=timeout; if write tool → no retry button
|
||
#endregion AgentChat.StreamProcessor
|
||
|
||
#region AgentChat.Types [C:2] [TYPE Module] [SEMANTICS agent-chat,types,svelte]
|
||
# @ingroup AgentChat
|
||
# @BRIEF Shared TypeScript types — UIContext, ToolAffinityEntry, ConfirmMetadataV2, PermissionDeniedMetadata.
|
||
|
||
// ADD interfaces (see data-model.md for full shapes):
|
||
// export interface UIContext { objectType, objectId, objectName, envId, route, contextVersion }
|
||
// export interface ConfirmMetadataV2 { ... with dangerous, env_context, permission_granted }
|
||
// export interface PermissionDeniedMetadata { type:"permission_denied", tool_name, required_role, alternatives }
|
||
// export type ToolCallStatus = "executing" | "retrying" | "completed" | "failed" | "timeout" | "cancelled";
|
||
// EXTEND ToolCall: +retryCount?, +maxRetries?, +timeoutSeconds?, +isWriteTool?
|
||
#endregion AgentChat.Types
|
||
|
||
#endregion ModulesContract
|