CAMEL-23978: Add AI panel slash commands to Camel TUI#24594
Conversation
1a21b09 to
35c511f
Compare
a39d990 to
2217053
Compare
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 8 tested, 8 compile-only — current: 6 all testedMaveniverse Scalpel detected 16 affected modules (current approach: 6).
|
davsclaus
left a comment
There was a problem hiding this comment.
Review: CAMEL-23978 — AI panel slash commands
Well-structured feature PR. Clean separation of concerns (AiSlashCommandRegistry, AiSlashCommandContext, AiCliCommandExecutor), thorough test coverage (concurrency edge cases, parsing, error paths, rendering), and good documentation update.
Positive highlights:
- Migrating
ConversationEntry.rolefromStringto the newAiRoleenum catches mismatches at compile time. - The settings persistence integrates naturally with the existing
TuiSettings/SettingsPopuppattern. - Architecture is independently testable per component.
Findings (inline comments below):
thinkingVerbfield data race — easy fix withvolatile.tryExplicitUrl()behavioral change — worth noting.- Minor scope:
Ask.javaformatter change mixed in.
This review evaluates the PR against the project's contribution rules and conventions. It does not replace specialized review tools (CodeRabbit, SonarCloud) or static analysis.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of davsclaus
gnodet
left a comment
There was a problem hiding this comment.
Good feature addition — clean architecture, thorough test coverage, and docs updated.
One finding:
AiSlashCommandRegistryTest.java — the NoopSlashContext inner class uses java.util.concurrent.CompletableFuture as a fully qualified class name inline (2 occurrences) instead of importing it. This violates the project coding convention (no FQCNs — always use imports).
Reviewed with Claude Code on behalf of gnodet. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.
gnodet
left a comment
There was a problem hiding this comment.
Claude Code review on behalf of @gnodet
Re-review after 2 new commits. Previous FQCN feedback addressed ✅. One new issue found.
Feedback addressed
- ✅
CompletableFutureis now properly imported inAiSlashCommandRegistryTest - ✅
thinkingVerbmarkedvolatilefor thread safety between UI and callback threads - ✅ Unrelated
printErrline wrap reverted
New issue: duplicate placeholder block in AiPanel.renderInput()
The placeholder rendering code is duplicated — the same block appears twice consecutively:
if (cursorPos == text.length()) {
Optional<String> placeholder = slashCommands.placeholderFor(text);
if (placeholder.isPresent()) {
spans.add(Span.styled(" " + placeholder.get(), Style.EMPTY.dim()));
}
}
if (cursorPos == text.length()) {
Optional<String> placeholder = slashCommands.placeholderFor(text);
if (placeholder.isPresent()) {
spans.add(Span.styled(" " + placeholder.get(), Style.EMPTY.dim()));
}
}This will render the placeholder hint text twice in the input line (e.g., /run would show <files...> [--dev] [--port=8080] [...] appended twice). One of these blocks should be removed.
Slash command placeholder feature
The placeholderFor() implementation and test coverage look good — clean design with proper Optional handling.
|
@gnodet Fixed in eebbb7b — removed the duplicated placeholder block in Claude Code on behalf of ammachado |
gnodet
left a comment
There was a problem hiding this comment.
Claude Code review on behalf of @gnodet
Duplicate placeholder fix verified ✅
- Duplicate
placeholderForblock removed fromrenderInput() - Test
commandPlaceholderRendersAfterTrailingSpacenow asserts the placeholder renders exactly once (viacountOccurrences), guarding against regression
No remaining concerns from my reviews. LGTM.
davsclaus
left a comment
There was a problem hiding this comment.
LGTM — well-executed feature addition.
Clean architecture with proper separation (AiSlashCommandRegistry, AiSlashCommandContext, AiCliCommandExecutor, AiProviderSelector), introduction of AiRole enum replacing error-prone string role identifiers, and comprehensive test coverage across all new classes.
All previous review findings from @davsclaus and @gnodet have been addressed in follow-up commits. No unresolved conversations remain.
Observations (non-blocking):
- Branch naming:
feature/ai-slash-commands— project convention isfeature/<ISSUE_ID>-<short-slug>(i.e.feature/CAMEL-23978-ai-slash-commands). Minor, for future reference. tryExplicitUrl()side-effect: Now probes/api/tagsand/v1/modelsbefore bare-URL fallback, settingapiTypeas a side-effect. Custom proxy/gateway URLs that don't serve those sub-paths get two extra probe round-trips. Documented in commit message — worth monitoring if users report slow startup with custom AI URLs.extractStringList()unchecked cast inLlmClient.listModels():(JsonArray) response.get(arrayField)is fail-safe (outer caller handles null returns) but silently swallows unexpected API response shapes.
Ready to merge (squash).
Claude Code on behalf of davsclaus
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
|
PR is back to draft since I've found a few issues testing. |
|
okay was about to, let us know when its ready next time |
gnodet
left a comment
There was a problem hiding this comment.
Claude Code review on behalf of @gnodet
Re-review: 3 new commits since last review
Commit ac20f92c — Launch /run and /infra as tracked background processes: Well-designed. /run and /infra run now go through LaunchManager (same path as F2 Actions), so they appear as tracked background processes instead of blocking the panel in-process. Key strengths:
LaunchSpecrecord withList.copyOf()for immutability- Infra auto-start before example launches (with
findMissingInfraServicescheck) launchDetachedQuietly()gracefully reports launch failures to the conversation/infra listcorrectly stays in-process while/infra rundetaches —isInfraRun()check is clean- Comprehensive test coverage:
RecordingLaunchContext,LaunchSpec.forRun()/forInfra()tests
Minor: exampleCatalog volatile field has a benign data race (two threads may load the catalog simultaneously) since there's no synchronization around the read-then-write. Harmless because ExampleHelper.loadCatalog() is idempotent and reference assignment is atomic, but worth noting.
Commit 822be6d5 — Persist /model selection: Clean fix. persistModelSelection() correctly updates both TuiSettings (disk) and sessionProviderChoice (memory), so neither a restart nor a panel re-init reverts the model. Test uses @TempDir with proper finally cleanup of CommandLineHelper.useHomeDir.
Commit 427d2b3c — TAB completion and clipped output fix: Two independent improvements:
- TAB completion: Nice state machine — longest common prefix first, then cycle, Shift+TAB for backward, any edit resets. The
completionSnapshotcomparison for detecting edits is clean and avoids explicit reset logic in every key handler. - Clipped output fix: Replacing character-count estimation with
MarkdownView.computeHeight()is the right fix — the old approach under-counted lines for markdown block elements (headings, lists, fenced code, blockquotes) that render with surrounding blank rows. Re-measuring at the narrower width after adding the scrollbar column is a good attention to detail.
One minor nit: The longResponseAutoScrollsToShowLastLine test uses Thread.onSpinWait() in a busy-wait loop. While onSpinWait() isn't Thread.sleep(), Awaitility would be more idiomatic:
await().atMost(5, TimeUnit.SECONDS).until(() -> !panel.isAgentThreadRunningForTesting());Approved — all three commits are well-implemented with thorough tests.
Co-authored-by: OpenAI Codex <codex@openai.com>
Pick the thinking verb with ThreadLocalRandom instead of a round-robin counter, and pick up the formatter-driven line wrap in Ask.java from the Task 6 verification pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Remove gemini/azure-openai provider choices that LlmClient.ApiType cannot represent, which threw IllegalArgumentException and left the panel permanently unusable once selected - Report provider-switch failures instead of a false "Switched to X" success message - Stop leaking a stale model string across a provider switch - Fall back Settings' AI Provider field to "auto" instead of silently defaulting to "ollama" for an unrecognized stored value - Close the provider popup when the panel closes and let it claim keys (including F8) while visible, so it can no longer get stuck open across a close/reopen cycle - Clamp provider popup dimensions to avoid a negative-size Rect on small terminals - Join the agent thread before interrupting or switching providers to avoid races with an in-flight request, and guard Ctrl+P/question submission while the agent thread is alive - Guard against submitting a question with no LLM client configured - Derive Settings' AI provider list from LlmClient.ApiType directly and add mouse support to the provider switch popup
Re-add isAgentThreadRunningForTesting() dropped during the merge with feature/ai-slash-commands so provider-switch and interrupt tests compile. Co-authored-by: Cursor <cursoragent@cursor.com>
Drop the task report file that was accidentally included in the branch. Co-authored-by: Cursor <cursoragent@cursor.com>
Addresses a comprehensive review of the AI panel slash command PR: - AiCliCommandExecutor.cancel() no longer permanently wedges the executor or blocks the caller for up to 30s; the cancellation timeout now runs on a watcher thread. - Sub-command stderr is now captured into the CLI output shown to the user instead of being swallowed by picocli's default handler. - /clear now also resets the LLM message context, not just the visible transcript. - /model no longer reports success when there is no client to apply the switch to. - Invalid persisted provider values now get an actionable message. - Fixed /help's self-referential defaults() recursion, added a fail-fast alias-collision check, removed the dead Descriptor.asynchronous field, broadened slash-command dispatch error handling, and made the conversation list thread-safe. - Introduced a shared AiRole enum for ConversationEntry/CommandResult so an unexpected role can no longer be silently dropped by the renderer. - Documented the /help command aliases and the /provider,/model busy behavior; minor cleanup (final modifier, ProviderChoice validation, clearer CLI executor failure messages). Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
stopAgentThread()'s thread.join(30_000) runs on the caller (the interactive UI thread on Esc/Ctrl+C, or destroy() during shutdown) and can block it for up to 30s if the agent thread's LLM HTTP call ignores the interrupt. Left blocking deliberately, unlike AiCliCommandExecutor.cancel(), because AiPanelTest asserts synchronous cancellation semantics; note the tradeoff for future reference. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Move the provider-choice building (env/settings ordering and de-duplication) and provider/model/url application logic out of AiPanel's private methods into a dedicated, independently-testable AiProviderSelector. Previously these rules could only be exercised through AiPanel's key-event handling and test-only escape hatches; they now have direct unit test coverage for the choice-ordering rules and provider-name validation. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Add LlmClient.listModels(), querying Ollama's /api/tags and the OpenAI-compatible/Anthropic /v1/models endpoints (Vertex AI has no listing endpoint, so it stays empty). Wire AiPanel's PanelSlashCommandContext.availableModels() to it so /model with no arguments shows real models instead of always an empty list. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Bring back the moving "..." suffix on the AI panel's thinking status (dropped when the verb-based thinking text replaced the old literal "thinking" label), keeping the new random thinking verbs. Also fix AiProviderSelector so the provider-switch popup always offers anthropic/openai/ollama for manual selection instead of hiding providers whose API key isn't currently detected in the environment; LlmClient already surfaces a clear error at request time if the selected provider's key is missing. Also drops an unused static import in CamelMonitor and labels assistant replies with "TUI:" in the AI panel conversation view. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The rebase onto origin/main pulled in an upstream "Log Pin" settings row that shifted the AI Provider row from index 3 to 4. Update aiRowsPersistProviderModelAndUrl to navigate to the new row index; behavior itself is unaffected, only the test's key-press count. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Append dimmed parameter hints inline when a slash command is typed with a trailing space and no parameters yet. Co-authored-by: Cursor <cursoragent@cursor.com>
- AiPanel: mark thinkingVerb volatile so its value is visible across the UI thread and the CLI completion callback thread (davsclaus). - AiSlashCommandRegistryTest: import CompletableFuture instead of using the fully qualified name, per the no-FQCN coding convention (gnodet). - Ask: revert the unrelated printErr line wrap to keep the diff focused; the single line is within the 128-char formatter limit (davsclaus). Note on detection behavior (davsclaus): LlmClient.tryExplicitUrl now probes "/api/tags" and "/v1/models" before the bare URL and sets apiType as a side effect when a probe succeeds. Custom proxy/gateway URLs that do not serve those sub-paths still fall through to the bare-URL reachability check, at the cost of two extra probe round-trips. Co-authored-by: Cursor <cursoragent@cursor.com>
The slash-command placeholder hint block in renderInput() was duplicated, rendering the placeholder twice on the input line (e.g. "/run " showed the argument hint appended twice). Remove the second block. Strengthen commandPlaceholderRendersAfterTrailingSpace to assert the placeholder renders exactly once; the previous contains() assertion passed even when it rendered twice. Reported-by: gnodet Co-authored-by: Cursor <cursoragent@cursor.com>
The AI panel's /run slash command executed camel run in-process, which blocked forever on a long-running application, locked the panel, and left the app untracked. This diverged from the F2 Actions menu, which spawns a detached, tracked process. Route /run and /infra run through the same LaunchManager the F2 menu uses: detached process, --name derived from a categorised --example, --logging-color, infra auto-start, and process tracking. Short in-process commands (/send, /infra list) no longer lock the panel into the thinking state, so the user can keep typing; Esc still cancels them. Also advertise --example in the /run placeholder. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The /model slash command only called client.withModel(...) in memory, so a model chosen via /model was lost on the next TUI start (only the Settings popup persisted it). Write the selection to TuiSettings and keep the in-session provider choice in sync so re-initialising the client does not revert it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add TAB completion for slash command names in the AI panel input. TAB first fills in the longest common prefix of the matching commands, then cycles forward through the matches (wrapping so every match is reachable with TAB alone); Shift+TAB cycles backward. A single match is completed fully with a trailing space. The cycle continues only while the buffer still equals the text the last TAB press produced, so any other edit transparently starts a fresh completion. Also fix the conversation view clipping the last couple of lines of an AI response. The scroll position was derived from a character-count line estimate that under-counts markdown block elements (headings, lists, fenced code, blockquotes render with surrounding blank rows the naive per-line count ignores), so auto-scrolling to the bottom left the newest lines hidden below the visible area. Use MarkdownView.computeHeight() for the exact rendered height instead, re-measuring at the narrower width when the scrollbar column is shown. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Codex <noreply@openai.com>
0db1778 to
ba40132
Compare
gnodet
left a comment
There was a problem hiding this comment.
Claude Code review on behalf of @gnodet
Re-review: New commit ba40132d — Harden AI model selection
Three clean changes:
-
LlmClient.extractStringList(): Replaced unchecked cast(JsonArray) response.get(arrayField)withinstanceofpattern matching. PreventsClassCastExceptionwhen a provider returns an unexpected shape (e.g.,{"models":{}}instead of{"models":[]}). Good defensive fix. -
New test
returnsEmptyListWhenModelListHasUnexpectedShape(): Covers the exact case — object instead of array. ✓ -
AiPanel.initClient(): Removed hardcoded.withModel("llama3.2"). Correct — a hardcoded Ollama-specific default doesn't belong in a multi-provider context. The model now comes fromsessionProviderChoiceor the provider's own default.
LGTM — still approved.
davsclaus
left a comment
There was a problem hiding this comment.
Nice work on this PR — the slash command architecture (registry, executor, context) is clean, the TAB completion is a thoughtful UX touch, and the test coverage across 7 new test files is excellent. A few observations below.
This review does not replace specialized tools such as CodeRabbit, Sourcery, or SonarCloud for deeper static analysis.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of davsclaus
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
gnodet
left a comment
There was a problem hiding this comment.
Claude Code review on behalf of @gnodet
Re-review: New commit d97d48fd — Address latest AI panel review feedback
Four well-motivated changes:
-
Non-blocking cancellation (
stopAgentThread()): Replaced the blockingt.join(30_000)on the UI thread with a daemon watcher thread. This prevents the TUI from freezing for up to 30s when an HTTP client ignores interruption. The agent thread's ownfinallyblock clears the thinking state, so removing the redundantthinking.set(false)fromstopAgentThread()is correct. -
CLI/LLM state separation: Removed
thinking.set(false)fromhandleCliCompletion(). Since CLI commands (like/send) no longer put the panel into thinking state (previous commit), clearing it on completion was wrong — it would accidentally kill an active LLM request's thinking indicator. The new testcliCompletionDoesNotClearLlmThinkingStateis the exact regression test for this. -
Awaitility migration: All
Thread.onSpinWait()busy-wait loops replaced withawait().atMost(...)— this directly addresses the review feedback from the TAB completion commit. Awaitility dependency added topom.xml. Five test methods updated consistently. ✓ -
Ollama detection rework: Replaced the fragile
inferApiTypeFromUrlHost()URL-pattern heuristic withisOllamaRoot(), which actually checks the server's response body for"Ollama is running". Much more reliable — the old approach guessed from URL substrings like "11434" or "ollama". New test with a real HTTP server covers non-default port detection.
LGTM — all changes are well-tested. Still approved.
Description
Adds local slash commands to the Camel TUI AI panel so operators can control the panel and invoke Camel CLI actions without sending prompts to the configured AI provider.
Highlights
/help,/provider,/model,/clear,/close,/quit(with/exit,/q,/xaliases)./run,/infra, and/sendrun through a TUI-safe executor with output capture and cancellation./runand/infra runlaunch as tracked background processes;/infra listremains in-process.Review follow-up
GETresponse (Ollama is running), so detection no longer relies on port11434and does not classify arbitrary reachable URLs as Ollama.cli_exectool to protect the shared Camel JBang/picocli instance./providerand/modelwhile either an AI request or background slash CLI command is active.Tracking
https://issues.apache.org/jira/browse/CAMEL-23978
Verification
mvn -Dtest=LlmClientListModelsTest testindsl/camel-jbang/camel-jbang-coremvn -Dtest=AskCliToolsTest,LlmClientListModelsTest testindsl/camel-jbang/camel-jbang-coremvn -Dtest=AiPanelTest testindsl/camel-jbang/camel-jbang-plugin-tuimvn -Dtest=AiCliCommandExecutorTest,AiPanelTest testindsl/camel-jbang/camel-jbang-plugin-tuiAI-generated by Codex on behalf of ammachado. Commits include
Co-authored-by: Codex <noreply@openai.com>.