Skip to content

feat: Drawer scrim (#286) + Run-now (#292) + Local AI tile (#300), v0.41.9 - #301

Merged
padak merged 3 commits into
mainfrom
feat/yustme-followup-286-292
May 15, 2026
Merged

feat: Drawer scrim (#286) + Run-now (#292) + Local AI tile (#300), v0.41.9#301
padak merged 3 commits into
mainfrom
feat/yustme-followup-286-292

Conversation

@padak

@padak padak commented May 15, 2026

Copy link
Copy Markdown
Member

Summary

Three Yustme/Vojta UAT follow-ups + the Kai → Local AI dashboard swap. 16 files touched, 3328 tests passing.

Issues addressed

Closed wontfix

Version

Rebased on top of b60e0a6 (v0.41.1 auto-update extras fix by PR #302). Bumped to 0.41.9 — leaves room for 0.41.2–0.41.8 to be assigned to other in-flight PRs.

Key engineering details

Architecture (3 helper-layer instances now share the same pattern):

  1. stream_ai_agent_events() (low-level primitive in agent_runner.py) — spawn CLI, yield SSE events.
  2. Stateless helpers — all three forward SSE events 1:1 (or with done-event enrichment):
    • POST /agents/prompt/improve/stream — rewrites agent task prompts
    • POST /workspaces/sql/improve/stream — writes workspace SQL with linked-bucket FQN handling
    • POST /ai/chat/stream (new) — generic chat
  3. Agent Tasks (/agents/...) — persistent + scheduled, built on top of layer 1, adds AgentTask identity + cron + run history.

Local AI meta-prompt is the most generic of the three: no output-shape constraint (chat renders markdown verbatim), no single-task framing. The kbagent skill (~70 KB) is NOT inlined — the AI is told to run kbagent context on demand, mirroring Claude Code's plugin loader.

Hand-off from Dashboard hero: UIState.pendingLocalAiMessage slot, set by Dashboard Ask button + read once by LocalAi page on mount + cleared. Single-shot auto-send through the sendRef imperative ref to bypass the input-state async race.

Test plan

  • make check → 3328 passed, 7 skipped
  • TypeScript build clean
  • Live: Drawer scrim visible in both light + dark modes against workspace detail
  • Live: Dashboard Run button fires existing POST /agents/{id}/run; refreshes row inline via React Query invalidation
  • Live: Local AI page receives streaming responses, transparency panels render meta-prompt + Activity log, markdown renders correctly
  • Live: Dashboard hero hand-off works (type → Send → /localai opens + auto-sends)
  • Backend tests: 15 new in test_local_ai_chat.py (11 meta-prompt content + 4 SSE endpoint integration with mocked stream_ai_agent_events)

Plugin docs synced

Out of scope (follow-ups)

@padak
padak force-pushed the feat/yustme-followup-286-292 branch from 7a80359 to 252c295 Compare May 15, 2026 12:57
@padak padak changed the title fix: lighter Drawer scrim (#286) + dashboard Run-now button (#292) feat: Drawer scrim (#286) + Run-now (#292) + Local AI tile (#300), v0.41.1 May 15, 2026
padak added 2 commits May 15, 2026 15:11
…uled-agents tile (#292)

#286 — Workspace detail overlay reported by yustme as 'looks like the
left half of the screen crashed'. The Drawer scrim was bg-zinc-950/90
(90% opaque) with a stale comment claiming 70% would 'let underlying
buttons bleed through and break modality'. That concern was visual,
not interactive — the click-catch <div className='flex-1' onClick=
{onClose}> blocks pointer events at the layout level regardless of
scrim opacity. Vojta's read was right: 90% reads as 'broken layout',
not 'modal opened'.

New scrim: bg-zinc-900/50 in light mode, bg-black/70 in dark mode,
both retain backdrop-blur-sm. Light-mode white background dims
visibly without erasing context; dark-mode zinc-950 page needs the
heavier 70% black to register against the slightly-lighter scrim.

#292 — Dashboard 'Scheduled agents' tile gets an inline 'run' button
per row (Agents page already had one). New ScheduledAgentRow extracted
from the inline <li> map. The button fires POST /agents/{id}/run
(blocking variant, same endpoint /agents/{id}/run path already used
by the Agents page Run drawer for non-streaming use), then invalidates
the ['agents'] and ['agent-runs', id] query keys so the row's
last_run_at + status pill flip live without a manual reload.

Deliberate choice: blocking call, not /run/stream + drawer. The tile
is a glance-and-move-on surface; users who want to watch tool_use
events in real time use the full Run drawer on Agents page (which
already does the SSE attach + replay).
…gotchas

Builds on the lighter Drawer scrim (#286) and dashboard Run-now button
(#292) already in the previous commit; this commit adds the Local AI
tile that replaces Kai on the dashboard (per the closing rationale on

Backend:
- POST /ai/chat/stream — third instance of the stateless-helper layer
  (after /agents/prompt/improve/stream and /workspaces/sql/improve/
  stream). Builds a generic chat meta-prompt and forwards SSE events
  from stream_ai_agent_events 1:1 — no SQL-style post-processing on
  the done event, no fence-stripping (markdown renders verbatim).
- build_local_ai_meta_prompt in agent_runner.py — the most generic of
  the three helper meta-prompts. No output-shape constraint, no
  single-task framing. Grounds the AI as a kbagent co-pilot with
  kbagent CLI on PATH + 'run kbagent context for command docs'
  pointer instead of inlining the ~70 KB skill into every request.

Frontend:
- pages/LocalAi.tsx — chat surface with CLI selector, abort, markdown
  rendering, per-message transparency panels (meta-prompt + tool_use
  activity log).
- Dashboard hero rewired: 'Ask the local AI…' input drops the message
  into UIState.pendingLocalAiMessage and navigates to /localai; the
  Local AI page auto-fires on mount. Avoids duplicating chat plumbing
  on the dashboard.
- Sidebar: 'Kai Chat' → 'Local AI'; page id 'kai' → 'localai'.
- pages/Kai.tsx deleted (Kai BACKEND endpoints remain available for
  callers; only the UI surface was swapped).

Architecture note: the chat helper consciously stays on layer 2
(stateless helpers) instead of layer 3 (persistent agent tasks). Multi-
turn history is a v2 follow-up that can either inject prior turns into
the meta-prompt (still layer 2) or migrate the chat to a persistent
agent task (layer 3) — the wire is the same.

Version: 0.41.0 was taken upstream by PR #293 (semantic-layer command
group), so this PR bumps to 0.41.1 after rebasing on top.

Changelog: new 0.41.1 entry covering Drawer scrim, Run-now button,
Local AI tile + endpoint, and gotchas.md notes.

Gotchas (since v0.41.1):
- Kai Chat web UI is gone; replaced by Local AI tile. /kai/* backend
  endpoints remain available — only the nav entry / dashboard tile
  flipped from Kai (per-project blocking) to Local AI (cross-project
  SSE-streaming through claude/codex/gemini).
- Dashboard's ▶ run button uses POST /agents/{id}/run (blocking) while
  the Agents page Run drawer uses POST /agents/{id}/run/stream (SSE
  with late-attach). Both persist the same AgentRun record.

Tests: +15 in tests/test_local_ai_chat.py (11 meta-prompt content, 4
SSE endpoint integration with mocked stream_ai_agent_events). Total
suite: 3155 passed.
@padak
padak force-pushed the feat/yustme-followup-286-292 branch from 252c295 to 17a4dc5 Compare May 15, 2026 13:16
@padak padak changed the title feat: Drawer scrim (#286) + Run-now (#292) + Local AI tile (#300), v0.41.1 feat: Drawer scrim (#286) + Run-now (#292) + Local AI tile (#300), v0.41.9 May 15, 2026

@padak padak left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of #301 — feat: Drawer scrim (#286) + Run-now (#292) + Local AI tile (#300), v0.41.9

Generated by kbagent-pr-reviewer subagent. Verdict and findings below
are advisory; the human author retains every veto. CI-coverable issues
(lint, format, tests) are confirmed via make check, not duplicated here.

Summary

PR adds three UAT follow-ups: a scrim opacity fix for the workspace detail Drawer (#286),
an inline ▶ run button on the Dashboard's Scheduled Agents tile (#292), and the main
feature — a new "Local AI" chat page at /localai that replaces the Kai Chat entry in
the dashboard and left nav (#300). The backend piece is a new FastAPI router
/ai/chat/stream (SSE, third instance of the stream_ai_agent_events stateless-helper
pattern), wired up with a build_local_ai_meta_prompt builder in agent_runner.py. Fifteen
new tests cover the meta-prompt content and SSE endpoint integration. The gotchas.md gains
two versioned entries. All plugin synchronization obligations for a no-new-CLI-command release
are correctly identified and discharged in the PR description.

Verdict: COMMENT. No blocking findings. Two non-blocking findings worth addressing
before tagging a human reviewer; both are low-risk future-proofing gaps.

Verdict

  • Verdict: COMMENT
  • Blocking findings: 0
  • Non-blocking findings: 2
  • Nits: 2

Blocking findings

(none)

Non-blocking findings

[NB-1] src/keboola_agent_cli/server/__init__.py:372/ai prefix absent from api_prefixes in _allow_static_through_auth

The _allow_static_through_auth helper's _is_ui_public function uses a hard-coded
api_prefixes tuple to distinguish "real API routes" from SPA client-side routes.
/ai (the new ai_chat router's prefix) is not in this tuple. Currently this is
harmless — the only endpoint under /ai is POST /ai/chat/stream, and the
_is_ui_public guard short-circuits on method != "GET" before consulting the list.
However, if a GET /ai/* endpoint is added later (e.g. a polling or status endpoint),
it would silently bypass authentication in --ui mode because _is_ui_public would
return True for it. All other routers added since --ui mode launched (/kai,
/semantic-layer, /members, /org) are already in the list. Add "/ai" to
api_prefixes for consistency and to close the latent gap.

Fix: add "/ai", to the api_prefixes tuple at src/keboola_agent_cli/server/__init__.py:386.

[NB-2] plugins/kbagent/agents/keboola-expert.md:106 — version gate not updated for POST /ai/chat/stream (0.41.9+)

The version gate in §1 Rule 6 lists serve-level features that are not present on
older installs (e.g. kbagent serve --ui needs 0.40.0+,
AI-agent run timeline persistence needs 0.40.0+). The new POST /ai/chat/stream
endpoint lands in v0.41.9. An AI agent invoking it via
kbagent http post /ai/chat/stream against a <0.41.9 install gets an HTTP 404 with
no diagnostic information. Adding a one-liner (POST /ai/chat/stream (generic Local AI co-pilot chat) needs 0.41.9+) to the version gate prevents that silent failure.

Fix: add to the version gate list at approximately line 107:

   `POST /ai/chat/stream` (generic Local AI co-pilot chat backed by the
   user's local claude / codex / gemini CLI; replaces the Kai tile in the
   web UI for non-master-token projects) needs 0.41.9+,

Nits

  • [NIT-1] src/keboola_agent_cli/server/routers/ai_chat.py:96"timeout": 300.0
    is a magic literal. The other helper endpoints also use inline literals (workspaces
    router uses 180.0, agents router uses 180.0 and 300.0). This is a pre-existing
    pattern — adding a AI_CHAT_HELPER_TIMEOUT constant to constants.py alongside the
    others would make it governable without hunting three files.

  • [NIT-2] src/keboola_agent_cli/server/routers/ai_chat.py:117-120except ValueError and except Exception both yield the exact same SSE done event body.
    Collapsing them into a single except Exception (or at minimum adding a comment
    explaining why they're separate) would reduce the surface area without any behavior
    change.

Verification log

  • gh pr view 301 --json title,body,files,additions,deletions,state → 17 files,
    +1208/-612, state OPEN, conventional feat: prefix matches multi-feature change ✓
  • git rev-parse --abbrev-ref HEADfeat/yustme-followup-286-292 ✓ (matches <branch>)
  • make check3328 passed, 7 skipped (exit 0) ✓
  • make typecheck → 440 diagnostics (exit 1), but grep ai_chat agent_runner.py → 0 hits;
    errors are pre-existing in agent_runner.py:1014 (_NullStore vs AgentStore type
    mismatch grandfathered from earlier work); no new errors introduced by this PR ✓
  • 3-layer grep: no typer/click imports in server/routers/ai_chat.py ✓; no
    httpx direct calls in new router ✓; stream_ai_agent_events correctly lives in
    agent_runner.py (server layer, not service or command layer) ✓
  • Plugin synchronization map: no new CLI commands → context.py, CLAUDE.md ## All CLI Commands, commands-reference.md correctly unchanged ✓. gotchas.md gains two
    (since v0.41.9) entries ✓. pyproject.toml, plugin.json, marketplace.json all
    at 0.41.9 ✓. changelog.py has a 0.41.9 entry ✓.
  • OPERATION_REGISTRY check: no new CLI commands → no new registry entry required ✓
  • --hint definitions check: /ai/chat/stream is a server-only route, not a CLI
    command → hint definition correctly excluded ✓ (matches "infrastructure category"
    exception in CONTRIBUTING.md)
  • api_prefixes gap: /ai not in the tuple at __init__.py:372; all active endpoints
    under /ai are POST-only so no live auth bypass exists today — latent gap only ✓
  • Version gate in keboola-expert.md: POST /ai/chat/stream not listed; existing
    serve-level entries cover 0.40.0 features; this endpoint is 0.41.9 — gap noted
    as NB-2 ✓
  • Behavior not reproduced live: kbagent serve --ui spawn would require a built
    web/frontend/dist which is not present in the working tree; the SSE integration is
    covered by the 15 unit tests with mocked stream_ai_agent_events
  • Token discipline: no tokens printed in new code; KBAGENT_SERVE_TOKEN referenced as
    an env var name only, not surfaced in output ✓
  • E2E tests: no E2E test for POST /ai/chat/stream; consistent with the existing
    pattern for other serve helper endpoints (/workspaces/sql/improve/stream,
    /agents/prompt/improve/stream) which also have no E2E tests. The E2E rule in
    CONTRIBUTING.md scopes to CLI commands, not serve-only endpoints ✓

Open questions for the author

(none)

…rsion gate, nits)

NB-1: `/ai` prefix was missing from the `api_prefixes` allow-list in
`_allow_static_through_auth` at `server/__init__.py:386`. Currently
harmless (only POST endpoints under `/ai` and `_is_ui_public` short-
circuits on `method != 'GET'` before consulting the list), but a
future GET endpoint under `/ai` would silently bypass auth in
`--ui` mode. Added the entry for consistency with every other
router prefix.

NB-2: `keboola-expert.md` Rule 6 VERSION GATE did not mention
`POST /ai/chat/stream (0.41.9+)`. An AI agent invoking it on an older
install gets a silent HTTP 404 with no diagnostic. Added a one-liner
to the gate so the agent refuses the task with a clear handoff message
instead of failing opaquely.

NIT-1: Helper subprocess timeouts (180.0 / 180.0 / 300.0) were inline
literals in three different routers (`agents.py`, `workspaces.py`,
`ai_chat.py`). Extracted to `constants.py` as
`AI_PROMPT_HELPER_TIMEOUT`, `AI_SQL_HELPER_TIMEOUT`,
`AI_CHAT_HELPER_TIMEOUT` so they're governable from one place.
Threshold rationale documented in the constants block.

NIT-2: `ai_chat.py` had two adjacent `except` clauses
(`ValueError` then `Exception`) yielding identical SSE `done`
event bodies. Collapsed to a single `except Exception` with a
comment explaining the catch-all rationale (must terminate the stream
on either ValueError-from-validation or runtime crash so the React
side never hangs on an unterminated stream).

`make check` still 3328 passed.
@padak
padak merged commit 23def16 into main May 15, 2026
1 check passed
@padak
padak deleted the feat/yustme-followup-286-292 branch May 15, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant