feat: Drawer scrim (#286) + Run-now (#292) + Local AI tile (#300), v0.41.9 - #301
Conversation
7a80359 to
252c295
Compare
…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.
252c295 to
17a4dc5
Compare
padak
left a comment
There was a problem hiding this comment.
Review of #301 — feat: Drawer scrim (#286) + Run-now (#292) + Local AI tile (#300), v0.41.9
Generated by
kbagent-pr-reviewersubagent. Verdict and findings below
are advisory; the human author retains every veto. CI-coverable issues
(lint, format, tests) are confirmed viamake 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 uses180.0, agents router uses180.0and300.0). This is a pre-existing
pattern — adding aAI_CHAT_HELPER_TIMEOUTconstant toconstants.pyalongside the
others would make it governable without hunting three files. -
[NIT-2]src/keboola_agent_cli/server/routers/ai_chat.py:117-120—except ValueErrorandexcept Exceptionboth yield the exact same SSEdoneevent body.
Collapsing them into a singleexcept 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, conventionalfeat:prefix matches multi-feature change ✓git rev-parse --abbrev-ref HEAD→feat/yustme-followup-286-292✓ (matches<branch>)make check→3328 passed, 7 skipped(exit 0) ✓make typecheck→ 440 diagnostics (exit 1), butgrep ai_chat agent_runner.py→ 0 hits;
errors are pre-existing inagent_runner.py:1014(_NullStorevsAgentStoretype
mismatch grandfathered from earlier work); no new errors introduced by this PR ✓- 3-layer grep: no
typer/clickimports inserver/routers/ai_chat.py✓; no
httpxdirect calls in new router ✓;stream_ai_agent_eventscorrectly 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.mdcorrectly unchanged ✓.gotchas.mdgains two
(since v0.41.9)entries ✓.pyproject.toml,plugin.json,marketplace.jsonall
at0.41.9✓.changelog.pyhas a0.41.9entry ✓. OPERATION_REGISTRYcheck: no new CLI commands → no new registry entry required ✓--hintdefinitions check:/ai/chat/streamis a server-only route, not a CLI
command → hint definition correctly excluded ✓ (matches "infrastructure category"
exception in CONTRIBUTING.md)api_prefixesgap:/ainot in the tuple at__init__.py:372; all active endpoints
under/aiare POST-only so no live auth bypass exists today — latent gap only ✓- Version gate in
keboola-expert.md:POST /ai/chat/streamnot listed; existing
serve-level entries cover0.40.0features; this endpoint is0.41.9— gap noted
as NB-2 ✓ - Behavior not reproduced live:
kbagent serve --uispawn would require a built
web/frontend/distwhich is not present in the working tree; the SSE integration is
covered by the 15 unit tests with mockedstream_ai_agent_events✓ - Token discipline: no tokens printed in new code;
KBAGENT_SERVE_TOKENreferenced 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.
Summary
Three Yustme/Vojta UAT follow-ups + the Kai → Local AI dashboard swap. 16 files touched, 3328 tests passing.
Issues addressed
backdrop-blur-sm. Click-catch was misdiagnosed: the layout<div className="flex-1">blocks pointer events regardless of opacity, so dimming was always purely visual.▶ runbutton per row. Uses blockingPOST /agents/{id}/run(the Agents page Run drawer keeps using the SSE/run/streamvariant — different UX, same persistence).Kai Chatswapped forLocal AI. New page at/localaiis a generic chat surface backed by the user's localclaude/codex/geminiCLI — third instance of the same stateless-helper pattern as the AI SQL helper and agent prompt helper. Closes the loop on org setup: projects registered via Manage API token cannot use Kai (missing master token) #291 (Kai's master-token requirement) and Kai Chat: multi-project context — cross-project comparison, migration assistant, and lineage root-cause tracing #288 (Kai's per-project structural limitation) — both already closed wontfix.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):
stream_ai_agent_events()(low-level primitive inagent_runner.py) — spawn CLI, yield SSE events.done-event enrichment):POST /agents/prompt/improve/stream— rewrites agent task promptsPOST /workspaces/sql/improve/stream— writes workspace SQL with linked-bucket FQN handlingPOST /ai/chat/stream(new) — generic chat/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 contexton demand, mirroring Claude Code's plugin loader.Hand-off from Dashboard hero:
UIState.pendingLocalAiMessageslot, set by DashboardAskbutton + read once by LocalAi page on mount + cleared. Single-shot auto-send through thesendRefimperative ref to bypass the input-state async race.Test plan
make check→ 3328 passed, 7 skippedPOST /agents/{id}/run; refreshes row inline via React Query invalidationtest_local_ai_chat.py(11 meta-prompt content + 4 SSE endpoint integration with mockedstream_ai_agent_events)Plugin docs synced
changelog.pygains a 0.41.9 entry covering all three issues + the docs sync (above the existing 0.41.1 entries from PR fix(0.41.1): auto-update hook preserves [server] extras + kbagent version refreshes cache #302)gotchas.mdadds (since v0.41.9) entries on the Kai → Local AI swap and the dashboard-vs-Agents-page Run-button protocol differencepyproject.toml+plugin.json+marketplace.jsonall bumped to 0.41.9 (viamake version-sync)Out of scope (follow-ups)