fix: new-chat model selection, JCEF module dependency, headless turn-test hang - #172
Merged
Conversation
OpenAI-compatible providers (Qwen, GPT, etc.) were truncating responses at 9-25s because the openai-tooling-prompt.md resource was missing, causing a silent fallback to incomplete tooling guidance. This incomplete guidance lacked critical OpenAI-specific instructions for: - Function-calling format specification - Streaming behavior and completion semantics - Tool approval workflows - Best practices for safe tool execution Without this guidance, non-Claude models emit malformed tool calls or misinterpret stream boundaries, causing the CLI to exit prematurely mid-response. Created src/main/resources/prompts/openai-tooling-prompt.md with comprehensive OpenAI-specific tooling guidance mirroring the structure used for other backends. Root cause: CliProcess.loadStaticPrompt() errors loudly when resources are missing, but OpenAiInstructions.build() silently falls back to buildFallbackToolingGuidance(). The fallback was intended as a temporary measure but lacked the model-specific instructions non-Claude backends require. Fixes: Qwen 3.6-35B-A3B and other non-Claude OpenAI-compatible providers now receive proper tooling instructions and no longer truncate mid-response. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…turn-test hang Model selection regression: a fresh chat showed the pinned model (e.g. Opus) in the dropdown but launched the CLI on its own default (Haiku) until the user manually re-picked. The dropdown reads the tab's AgentSelection.modelId while the Claude/Codex backends re-derived --model from the selectedModels settings map, which is empty when the default was set via the Roles tab. CliProcess now honors the tab's pinned model id (threaded through AgentBackendFactory), falling back to the settings-map read only when blank; the Codex branch mirrors this. JCEF NoClassDefFoundError: JBCefJSQuery$Response lives in the separate com.intellij.modules.jcef module, which ClawDEA never declared. Without it the jcef module is absent from the plugin classloader and ChatPanel.<init> throws on installs where JCEF is not already on the shared classpath. Add the dependency. Headless turn-test hang: the three OpenAiCompatibleAgentBackend turn tests hung when run in a fork without an IntelliJ Application, because a running turn read ClawDEASettings.getInstance() and the dying coroutine never emitted a terminal Result, blocking the queue reader forever. Add a settingsProvider test seam so turns are self-sufficient headless, plus a bounded @test timeout as a backstop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
|
Token usage on this PR
Totals — Input: 145k · Cache read: 690642k · Cache create: 40353k · Output: 4804k |
…vider Applying settings (e.g. setting Roles → Chat default to Opus) rebuilt any open chat tab onto the GLOBAL effective provider. ChatPanel.onSettingsChanged read AuthManager.effectiveProviderId() (global = Qwen), compared its backend kind to the tab's own backend, saw they differed, and called rebuildSessionForBackendChange(), reseeding the tab from the global provider. So an open Opus chat flipped to Qwen on any settings apply — the Roles change was incidental. A settings apply must never change an already-open tab's provider/backend: the tab keeps its own per-tab AgentSelection and only NEW chats adopt a changed global default. onSettingsChanged now only restarts the SAME bridge in place to pick up non-provider settings (tool-approval mode, toggles, CLI path/args, wiki model); CliProcess re-reads the tab's pinned model on restart, so provider and model are preserved. The global-provider-based rebuild coordinator is replaced by a pure settingsApplyAction() decision (RESTART_IN_PLACE / NONE), and the now-unused deferred-rebuild path (onTurnBecameIdle flush, no-arg rebuildSessionForBackendChange) is removed. Also includes an in-flight enhancement: a failed wiki-librarian subagent now prepends its WIKI-role model to the error summary for at-a-glance diagnosis. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the WIP begun with the EventStreamHandler librarian-error change: a stale WIKI-role model (e.g. Bedrock Claude 3 Haiku, which rejects prompt caching with an HTTP 400) silently broke the in-chat wiki librarian, and the surfaced error named neither the model nor the cause. Root-cause fix — smart per-role defaults (RoleDefaults/RoleDefaultsResolver): compute Chat / Wiki / Completions from whichever provider is authenticated — latest Opus for chat + latest Haiku for wiki/completions on Claude, Terra/Luna on Codex, first agentic model on openai-compatible — so each role stays on a caching-capable, task-appropriate model. RoleSelectionStore seeds these on fresh install (falling back to the legacy clone-across-roles when nothing is authenticated), and the Roles settings tab gains a "Reset defaults" button. Diagnostics: AgenticLibrarian now names the failing model in its error (mirroring the in-chat subagent path in EventStreamHandler), so an unsupported-model 400 is legible at a glance instead of an opaque failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The skill-invocation probe was hardcoded true, so every backend forwarded '/skill-name' as literal text. OpenAI-compatible and Codex backends have no CLI-native slash resolver, so skills silently did nothing. Gate the native branch on BackendKind.CLAUDE_CLI; other backends take the SKILL.md-injection fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture session skills in start(); advertise the Skill tool (gated on preloadSkillCatalog + non-empty skills) and route Skill calls to SkillTool. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ture) Regression guard: currentSkills must be captured on every start(), incl. resume, so the Skill tool is advertised after a resumed session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the false 'IDE will intercept slash text' claim; instruct the model to invoke skills via the Skill tool. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wrong-typed or null "name"/"args" now return a soft tool-error instead of throwing out of the executor and ending the turn (matching the Bash branch). Restore the dropped "proceed without skills if none listed" prompt caveat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
propose_write / apply_patch could report a file as written when it never landed on disk: EditDiffReviewer.applyContent returned Unit and silently bailed when parent.mkdirs() failed, and reviewAndRespond / HostPatchTool reported ACCEPTED regardless. Non-Claude backends (Qwen) also emit relative file_path args, which File(path) resolved against the IDE JVM CWD (~ "/"), so writes mislanded or apply_patch wrongly rejected them as "outside project". - applyContent now returns Boolean and resolves relative paths against the project base (mirrors PsiUtils); callers surface a truthful error on false. - HostPatchTool normalizes relative file_path against projectBasePath before the path-inside-project check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On non-Claude backends a user-typed /skill fell back to injecting the full SKILL.md as the bridge payload, which ChatPanel rendered verbatim as a user bubble. Now the fallback renders a compact "Using skill /name" chip and dispatches the markdown via CommandContext.dispatchToBridge (renderInChat=false, mirroring BridgeExpandingHandler) so the model still receives the full skill text. Falls back to the plain (rendering) send only when no hidden-dispatch channel is available (headless/tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ackend Agentic OpenAI-compatible models had no way to dispatch a sub-agent, so skills like subagent-driven-development narrated "dispatching..." and stalled. Add an Agent tool that runs a depth-1 nested tool loop and streams its steps into the existing sub-agent card (SubAgentController already recognizes the "Agent" tool and routes children by parentToolUseId). - AgentLoopController gains an optional SubAgentRunner; a tool call named "Agent" is routed to it (suspend + emit) instead of the synchronous executor, so the executor interface and all implementors are untouched. - SubAgentDispatcher runs a fresh nested turn, re-tags child events with the dispatching tool_use id, swallows the nested terminal Result, and returns the sub-agent's final report as the tool result. Fail-soft on missing prompt / malformed args. Sub-agents are NOT given the Agent tool (no recursion). - OpenAiToolCatalog.agentToolDefinition() advertises it (subagent_type/description match the card fields; prompt is the task). Gated on a new enableOpenAiSubagents setting (default on). - openai-tooling-prompt: document the Agent tool + require absolute file paths and actually writing spec/plan files instead of pasting them into chat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the completions model dropdown from ProvidersTab to AdvancedTab since the Roles tab already handles per-role model selection and the behavioral settings (enabled/debounce/manual-only) are global, not provider-scoped.
Since the Roles tab now handles per-role model selection and completions settings are global (not provider-specific), move the model dropdown from ProvidersTab to AdvancedTab alongside the existing behavioral toggles.
…ompatible Weak/limited-context models (e.g. 128K Qwen) degraded into repetitive tool-call loops mid-turn. Root cause: the model's context window was unknown, so the agent loop used a ~1M-char compaction proxy that exceeded the real window — compaction never fired and history overflowed. There was also no UI to set a window: the models table had no such column, and ContextWindows read only profile JSON. - Add a "Context window" column to the OpenAI-compatible models table (ModelEntry.contextWindow + OpenAiModelTableModel); preserved across /models refresh by both catalog mergers. - ContextWindows.resolve(): catalog column → profile map → conservative 128K default (never null). The backend passes the resolved window so the loop always uses the token budget. When a provider reports no usage, the loop estimates tokens from chars against the same window so compaction still fires. - ToolCallLoopGuard: detects a run of identical FAILING tool calls and escalates nudge → clean stop, breaking degeneration loops without erroring the turn. Sub-agent dispatches are exempt; a success resets the streak; distinct args are distinct signatures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ModifiedFrom Complete the wiring for the new completionsModelCombo so it persists the selected model to ClawDEASettings.State.completionsModel on apply and reads it back on load.
…Behavior panel, fix CLI path visibility for all providers
…hardcoded completions model, extract CLI settings, fix CLI path visibility
Makes ask_wiki_librarian work for every WIKI-role provider, fixing the "OpenAI-compatible profile '' not configured" crash when a Codex or Claude WIKI role invoked the tool, and adds a genuine Codex execution path. chooseLibrarianExecution tiers the handler by WIKI provider: - Claude -> ClaudeSubprocessLibrarian (claude -p, allowlisted MCP tools) - openai -> AgenticLibrarian in-process loop - Codex -> CodexExecLibrarian (NEW): codex exec --json CodexExecLibrarian runs read-only by construction: -s read-only with approval_policy=never and NO MCP server. The exec spike proved MCP tools only run under danger-full-access (any sandbox blocks the loopback socket on macOS), which would leave codex's shell ungated -- so the librarian reads the on-disk wiki and greps the tree with codex's built-in shell instead. Read commands need no escalation; a write escalation fails under never (no hang, no write). Tradeoff: no record_wiki_suggestion, no index tools -- acceptable for read-only Q&A. Chat routing keys on the CHAT backend: Codex/openai chats (which cannot spawn the --agents subagent) advertise the ask_wiki_librarian MCP tool; the shared primer anchor is mechanism-neutral. Verified: compileKotlin + compileTestKotlin green; 18/18 unit tests pass (CodexExecLibrarianTest 7, LibrarianExecutionTest 6, ClaudeSubprocessLibrarianTest 5); read-only sandbox confirmed live. Full end-to-end Codex answer not yet proven live (OpenAI workspace spend cap). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chat model selector was noticeably wider than its content. Two independent Swing sizing quirks each added slack: - The collapsed combo sized itself via prototypeDisplayValue, which routes through the cell renderer and picks up ~50px of internal padding; and its widest row was a signed-out third-party profile (long name + "(sign in)" suffix) the user never sees selected. Now the combo width is pinned to the widest *enabled* label + arrow + insets + a small margin. - BasicComboPopup.show() hardcodes the popup width to the combo's own width (arrow button included). The popup is now resized to its list content width when shown. Also refresh the selector on subscription/Codex auth status changes so newly-signed-in providers become selectable without reopening the tab. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NotesPaths/SessionScanner/TranscriptCostReader encoded the project base
path with `"-" + trimStart('/').replace("/", "-")`, which only stripped
forward slashes. On Windows the drive colon survived (e.g. `-C:-Users-…`)
and Path.resolve threw InvalidPathException, silently dropping the notes
primer section. It also failed to locate sessions for any POSIX path
containing `.` or `_` (e.g. `.claude/worktrees` sessions).
Centralize the scheme in ClaudeProjectDir.encode(), which replaces every
non-alphanumeric char with `-` — verified against real CLI-created dirs
(`.claude` → `--claude`, `enc.test_dir` → `enc-test-dir`). Route all three
consumers through it so the plugin points at the exact directory the CLI
creates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…md.exe cap) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The interactive chat CLI no longer injects wiki --agents; the librarian is reached via the ask_wiki_librarian MCP tool and the author via the out-of-band WikiAuthorInvoker. Update both pages to drop the deleted chooseLibrarianMode / CLAUDE_SUBAGENT* framing and describe the single MCP-tool dispatch path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Delete dead prompts/wiki-librarian-prompt.md (the interactive librarian now uses WIKI_LIBRARIAN_TOOL_PROMPT; the subprocess librarian loads /agents/wiki-librarian.md) and its now-orphaned resource test. - Fix stale WIKI_LIBRARIAN_PROMPT comment references → WIKI_LIBRARIAN_TOOL_PROMPT. - runAuthorNow KDoc: describe the invoker generically (not "--agents"), since the openai-compatible/codex invokers don't use --agents. - Drop a stray double blank line in ChatPanel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…not merely unused Reflects that the subagent-persona constant and its prompt resource are now gone entirely (Task 4 + the minor-cleanup sweep), not "still exists but unreferenced". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The one-shot claude/codex subprocesses redirected stdin from a hardcoded
File("/dev/null") to hand the process immediate EOF (it otherwise blocks on
an open stdin pipe). On Windows "/dev/null" is a non-existent relative path,
so Redirect.from throws at process start — the in-chat wiki-librarian tool,
the codex librarian, and the out-of-band wiki-author all failed on Windows.
Centralize the null-device path in NullDevice (NUL on Windows, /dev/null
elsewhere) and route all three ProcessBuilder stdin redirects through it,
preserving the immediate-EOF behavior cross-platform.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ab model Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Provider/service/profile resolution before the runner call was unguarded; a throw there would escape the /seed-wiki coroutine (no CoroutineExceptionHandler) and leave a dangling "Seeding…" line with no resolution. Wrap setup+run so any failure surfaces as a Result(ok=false) the caller renders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…horing entry point Adds the seed-wiki path (WikiPromptRunner, tiered by WIKI-role backend, used by /seed-wiki to bootstrap under the WIKI model) alongside rescan auto-apply and runAuthorNow. Produced by the drift auto-author; verified accurate against source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 22, 2026
OpenAI-compatible provider: sub-agents, Skill tool, roles-tab defaults, capability-resolver fix
#173
Closed
spopescu
added a commit
that referenced
this pull request
Jul 22, 2026
…0 failures) (#175) * fix(deps): pin kotlinx-coroutines-test to the platform's coroutines-core fork Two same-named kotlinx.coroutines.BuildersKt classes were on the test classpath simultaneously: the IntelliJ Platform's own forked coroutines-core (adds runBlockingWithParallelismCompensation, needed by ComponentManagerImpl's service init) and the vanilla upstream jar kotlinx-coroutines-test:1.11.0 pulls in transitively (needs runBlockingK instead, which the platform's fork never had). Whichever copy the flat PathClassLoader bound first "won" nondeterministically across a full ./gradlew test run, breaking whichever caller needed the other — surfacing as ~32 NoSuchMethodError failures. Pin kotlinx-coroutines-test to 1.10.2, matching the platform fork's base version (whose calls the fork satisfies), and exclude the transitive vanilla kotlinx-coroutines-core/-core-jvm at the configuration level so the platform's fork is the only BuildersKt on the classpath. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: repair 4 pre-existing test-suite failures - RoleSelectionStoreTest: the migration test depended on real machine auth state (macOS Keychain "Claude Code-credentials") instead of mocking AuthManager, so it failed on any machine with live Claude subscription credentials. Add a test seam (RoleSelectionStore.migrateFromLegacyIfNeeded/applyDefaults now accept an injectable AuthManager) and inject a fake reporting no authenticated providers. - SeedWikiHandlerTest: a stale comment assumed no IntelliJ Application is registered so the handler's executeOnPooledThread fallback runs synchronously, but the Platform test harness always registers a real one, making the dispatch genuinely async and creating a race the test always lost. Wait on a CountDownLatch instead of asserting immediately. - OpenAiCompatibleIntegrationTest: the expected advertised-tools set was never updated after PR #172 added the "Agent" sub-agent tool (advertised by default via enableOpenAiSubagents). - ChatHtmlTemplateTest: asserted the pre-refactor finalizeReasoning() body (moving reasoning into a collapsed history block); commit c9d597a rewrote it to an intentional no-op and removed the related CSS, but the test wasn't updated to match. Full suite: 2309 tests, 0 failures/errors/skipped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four fixes, plus the previously-unpushed main commit
33a7a90(rescued here since main is protected).1. New-chat model selection regression
A fresh chat showed the pinned model (e.g. Opus) in the dropdown but launched the CLI on its own default (Haiku) until the user manually re-picked.
Root cause: the dropdown reads the tab's
AgentSelection.modelId, while the Claude/Codex backends re-derived--modelfrom theselectedModelssettings map — empty when the default was set via the Roles tab (which writesroleSelections, neverselectedModels). So no--modelwas passed and the CLI fell back to its own default.Fix:
CliProcesshonors the tab's pinnedselection.modelId(threaded throughAgentBackendFactory), falling back to the settings-map read only when blank. Codex branch mirrors this.2. JCEF
NoClassDefFoundError: JBCefJSQuery$ResponseCrashed on some machines (JCEF disabled / JBR without CEF) while working on others — same IDE version.
Root cause:
JBCefJSQuery$Responselives in the separatecom.intellij.modules.jcefplatform module, which ClawDEA never declared. Without it the jcef module is absent from the plugin classloader andChatPanel.<init>throws the moment the tool window opens.Fix: add
<depends>com.intellij.modules.jcef</depends>toplugin.xml.3. Headless turn-test hang
OpenAiCompatibleAgentBackendrestart/retry/steering tests hung when run in a fork without an IntelliJApplication.Root cause: a running turn read
ClawDEASettings.getInstance(); headless, that threw inside the turn coroutine, which then never emitted a terminalResult, so the tests' unboundedreadEvent()loop blocked forever.Fix: add a
settingsProvidertest seam (matching the existing DI style) so turns are self-sufficient headless, plus a bounded@Test(timeout=…)backstop.4. Settings apply migrated an open chat to the global provider
Setting Roles → Chat default to Opus (or any settings apply) rebuilt the currently-open chat onto the global provider (e.g. Qwen).
Root cause:
ChatPanel.onSettingsChangedfired on every apply and realigned the open tab to the globaleffectiveProviderId(), ignoring the tab's own per-tabbridge.selection. Backend kinds differed →rebuildSessionForBackendChange(null)reseeded the tab from the global provider. Predates the per-tab selection model.Fix: a settings apply never changes an open tab's provider/backend — it only restarts the same bridge in place to refresh non-provider settings (tool-approval mode, toggles, CLI args, wiki model); the tab keeps its provider/model, and only NEW chats adopt a changed global default. The global-provider rebuild coordinator is replaced by a pure
settingsApplyAction()decision.Test plan
CliProcessModelSelectionTest— pinned-model precedence + blank fallbackSettingsApplyDecisionTest— settings apply restarts in place, never rebuildscompileKotlin,buildPlugin,verifyPluginProjectConfigurationgreen; no IDE diagnosticsrunIdelaunches with the JCEF dependency; JCEF helper processes runNotes
<depends>com.intellij.modules.jcef>makes JCEF a hard requirement — correct, since the whole chat UI is JCEF-based, but ClawDEA won't load on an IDE with JCEF disabled (rather than loading and crashing).NoSuchMethodError: runBlockingWithParallelismCompensationfailures in the full test suite are a pre-existing coroutines/platform-test-framework binary clash (present on a clean tree), unrelated to these changes.🤖 Generated with Claude Code