OpenAI-compatible provider: sub-agents, Skill tool, roles-tab defaults, capability-resolver fix - #173
Closed
spopescu wants to merge 42 commits into
Closed
OpenAI-compatible provider: sub-agents, Skill tool, roles-tab defaults, capability-resolver fix#173spopescu wants to merge 42 commits into
spopescu wants to merge 42 commits into
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>
…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>
AgentBackendFactory (and the wiki-librarian/drift authoring paths) resolved model capability from profile modelRules only, ignoring the per-model capability that "Refresh Models" already verified and persisted in the catalog. A model probed as agentic with no matching profile rule (e.g. NVIDIA NIM models outside a narrow rule list) fell through to COMPLETION_ONLY and the backend refused to start. Add ModelCapabilityResolver.catalogCapability to read a DEFINITE verified verdict (agentic/completion_only) from the catalog and feed it into resolve()'s endpointCapability slot, which already outranks profile rules. An unknown/absent entry still yields null so profile rules decide — a failed probe must never override a user-authored rule. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Collaborator
Author
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
Accumulated work on the OpenAI-compatible provider integration, plus a capability-resolution bugfix:
AgentBackendFactoryand the wiki-librarian/drift authoring paths resolved model capability from profilemodelRulesonly, ignoring the per-model capability already verified and persisted by "Refresh Models". A model probed as agentic with no matching profile rule (e.g. most NVIDIA NIM models) wrongly fell through toCOMPLETION_ONLYand the backend refused to start. AddedModelCapabilityResolver.catalogCapabilityto feed the verified verdict intoresolve()'sendpointCapabilityslot (which already outranks profile rules per the documented precedenceuserOverride > endpointCapability > matchingRule > COMPLETION_ONLY).Agent(sub-agent) tool for the OpenAI-compatible backendSkilltool support for OpenAI-compatible models, with fail-soft arg parsing and compact skill-injection chipsask_wiki_librarianwith a real Codex librarian path;/refresh-wikinow runs as an out-of-band wiki-author subprocessTest plan
./gradlew compileKotlin compileTestKotlincleanModelCapabilityResolverTest(7 tests) andCliBridgeBackendSelectionTest(12 tests) green, 0 skipped/failed — includes new regression coverage for the capability-resolver fix (verified-agentic, unknown/absent, completion-only cases, and a factory-level NVIDIA NIM repro)🤖 Generated with Claude Code