Skip to content

feat(providers/qoder): add native Qoder provider backed by qodercli - #1107

Open
kanmars wants to merge 17 commits into
siteboon:mainfrom
kanmars:feature/qoder-provider
Open

feat(providers/qoder): add native Qoder provider backed by qodercli#1107
kanmars wants to merge 17 commits into
siteboon:mainfrom
kanmars:feature/qoder-provider

Conversation

@kanmars

@kanmars kanmars commented Aug 5, 2026

Copy link
Copy Markdown

Summary

This PR adds a first-class qoder provider that drives chat sessions through the local qodercli binary, bringing the Qoder CLI into the same provider model as Claude, Codex, Cursor, and OpenCode. It implements all seven provider facets (runtime, models, auth, mcp, skills, sessions, sessionSynchronizer) and wires the provider into the chat composer, settings, permissions panel, and session synchronization pipeline.

What changed

Backend provider implementation (server/modules/providers/list/qoder/)

  • qoder.provider.ts — new wrapper registering all Qoder facets with AbstractProvider.
  • qoder-runtime.provider.js — executes qodercli with deterministic argv ordering, safe cwd encoding via encodeQoderCwd, session id validation, and support for image attachments in both runtime messages and session history.
  • qoder-models.provider.ts — lists available Qoder models from qodercli --list-models, with stable row parsing by shape, memoized catalog, and fallback metadata.
  • qoder-auth.provider.ts — reports install/auth status and exposes token-based login for the provider.
  • qoder-mcp.provider.ts — reads/writes Qoder-native MCP server config from ~/.qoder/settings.json (user scope) and .mcp.json (project scope), supporting stdio and http transports.
  • qoder-skills.provider.ts — discovers SKILL.md files from Qoder, Claude, and Agents skill roots, emitting /skill-name commands.
  • qoder-sessions.provider.ts — normalizes live runtime events and fetches session history with unique message ids, correct pagination, and title priority handling.
  • qoder-session-synchronizer.provider.ts — scans ~/.qoder/projects/**/*.jsonl, encodes working directories safely, and upserts sessions into sessionsDb with title recovery from ai-title rows or the first user prompt.

Shared infrastructure

  • Added Qoder-specific helpers in server/shared/utils.ts for transcript paths, jsonl handling, cwd encoding, and file timestamps.
  • Updated provider-capabilities.service.ts and provider-token-usage.service.ts to recognize Qoder-specific capabilities and usage shapes.
  • Updated sessions-watcher.service.ts and session-synchronizer.service.ts to include Qoder in the sync/watcher pipeline.

Module wiring

  • Registered qoder in provider.registry.ts and provider.routes.ts.
  • Added Qoder to server/shared/types.ts LLMProvider and src/types/app.ts LLMProvider.
  • Updated server/index.ts for runtime lifecycle wiring.
  • Updated server/modules/agent/agent.routes.ts so agents can launch Qoder sessions.
  • Updated server/modules/commands/commands.routes.ts and websocket/services/shell-websocket.service.ts where provider parsing needed extension.
  • Added Qoder to public/api-docs.html PROVIDER_ORDER.

Frontend

  • Added the Qoder brand icon (src/assets/qoder.png) and QoderLogo component.
  • Updated ProviderSelectionEmptyState, SessionProviderLogo, and chat provider state hooks to include Qoder.
  • Extended ProviderLoginModal for Qoder auth/setup flow.
  • Added Qoder-specific permissions UI in PermissionsContent and wired tools settings to the CLI.
  • Added Qoder constants to src/components/mcp/constants.ts.
  • Updated useSettingsController and AgentConnectionsStep to surface Qoder MCP/skill configuration.
  • Updated i18n strings for English, Simplified Chinese, and Traditional Chinese.

Tests

  • server/modules/providers/tests/qoder-args.test.ts
  • server/modules/providers/tests/qoder-models.test.ts
  • server/modules/providers/tests/qoder-permissions.test.ts
  • server/modules/providers/tests/qoder-sessions.test.ts
  • server/modules/providers/tests/qoder-session-synchronizer.test.ts

Also updated existing tests that needed Qoder-specific stubs or provider-order assertions.

Why

Qodercli is a standalone CLI product with its own session store, model catalog, MCP config, and skill layout. Treating it as a native provider lets users pick Qoder in the chat composer, reuse the existing session sidebar/sync infrastructure, and manage Qoder-specific permissions and MCP servers from the same settings UI as the other providers.

Screenshots

image image image image image image image

Test plan

  • npx tsc --noEmit -p server/tsconfig.json
  • npx eslint server/modules/providers/list/qoder/**/*.ts server/shared/types.ts server/shared/utils.ts
  • npx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-args.test.ts
  • npx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-models.test.ts
  • npx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-permissions.test.ts
  • npx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-sessions.test.ts
  • npx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-session-synchronizer.test.ts
  • Manual: select Qoder in the chat composer, verify model list loads and a message streams back
  • Manual: send a message with an image attachment and confirm it is passed to qodercli
  • Manual: open Settings > Permissions for a Qoder agent and toggle capabilities
  • Manual: verify Qoder sessions appear in the sidebar and sync from ~/.qoder/projects

Notes for reviewers

  • The Qoder runtime uses a .js file to match the existing provider runtime pattern (Claude/Codex/Cursor/OpenCode runtimes are also .js).
  • Cwd encoding replaces / with - so Qoder transcript paths remain safe on disk; this is shared with the session synchronizer via encodeQoderCwd.
  • The permissions panel is intentionally aligned with real qodercli behavior rather than reusing the generic capability list.

Summary by CodeRabbit

  • New Features
    • Added Qoder support across chat, onboarding, agent connections, sessions, terminal workflows, skills, authentication, and MCP.
    • Added model discovery, reasoning effort, session history, synchronization, token usage, and configurable tool permissions.
    • Added provider selection, model persistence, branding, and localized English, Simplified Chinese, and Traditional Chinese text.
  • Documentation
    • Updated API and provider documentation with Qoder setup, capabilities, MCP configuration, and usage details.
  • Bug Fixes
    • Improved shared file-existence handling for provider integrations.

baolong.bl added 15 commits August 4, 2026 17:27
Implement a first-class QoderProvider for CloudCLI, modeled after the
opencode provider, so CloudCLI can run via qodercli without the
CLAUDE_CLI_PATH wrapper trick:

- qoder.provider.ts: provider factory wiring runtime/models/auth/mcp/
  skills/sessions/sessionSynchronizer facets
- qoder-runtime.provider.js: spawn qodercli -p --output-format
  stream-json; permission-mode mapping (bypass_permissions /
  accept_edits); per-message attachments; session registration via
  stream events; JSONL-based token usage; per-session abort
- qoder-models.provider.ts: models from qodercli --list-models with
  fallback to QODER_FALLBACK_MODELS (Auto/Ultimate/Performance/
  Efficient/Lite/Cantus)
- qoder-auth.provider.ts: qodercli --version + ~/.qoder/.auth/user
  credential check
- qoder-sessions.provider.ts: normalizeMessage + fetchHistory from
  ~/.qoder/projects JSONL (thinking/text/tool_use/tool_result)
- qoder-session-synchronizer.provider.ts: scan ~/.qoder/projects for
  new JSONL sessions, upsert into sessionsDb with ai-title naming
- qoder-mcp.provider.ts: user scope from ~/.qoder/settings.json
  mcpServers, project scope from .mcp.json (stdio + http transports)
- qoder-skills.provider.ts: project + user skill sources (qoder/
  claude/agents conventions)
- provider.registry.ts: register qoder; UI wiring for provider
  selection, auth modal, token usage, sessions, api-docs, i18n
- tests: extend provider enumeration assertions to include qoder
- models: qodercli --list-models prints a literal MODEL header row;
  parseQoderModelsStdout now drops it so it cannot surface as a
  selectable model (selecting it passes -m MODEL and qodercli rejects
  it with exit 42). Adds a parser regression test against the real
  captured output.
- runtime/sessions: make the runtime the single owner of the
  session_created event. readQoderSessionId now reads sessionID /
  sessionId / session_id so the live system/init control event is
  captured regardless of key spelling, and the sessions provider init
  branch no longer emits session_created (matches claude/opencode
  siblings).
…y id

qodercli treats --session-id as 'create a session with this id' and rejects
ids that already exist on disk with 'Session ID ... is already in use.'
Continuing a conversation must pass --resume <providerSessionId> instead.
Replace the placeholder inline SVG with the official qoder.png (bundled via
src/assets so Vite hashes it and rewrites the URL per deployment base).
QoderLogo keeps its className API, so all SessionProviderLogo call sites
are unchanged.
The token usage service derived Qoder transcript directories with the
Claude-style replace(/[^a-zA-Z0-9-]/g, '-'), but qodercli only folds
'/' -> '-'. Project paths containing dots (e.g. /home/admin/my.app)
resolved to a nonexistent directory and surfaced as SESSION_FILE_NOT_FOUND,
silently breaking token accounting.

Add encodeQoderCwd to shared utils as the single source of truth for the
on-disk encoding and route the models provider fallback through it too,
so every transcript-path derivation stays consistent with what the CLI
writes.
…ings to CLI

Settings > Qoder > Permissions previously rendered blank because
AgentCategoryContentSection only had claude/cursor/codex branches. Add a
qoder branch reusing the tool-list panel (skipPermissions + allowed/
disallowed tools), persisted under the qoder-settings localStorage key the
chat composer already reads.

Backend: migrate the qoder permission mapping from the JS runtime into a
new TypeScript module (qoder-permissions.provider.ts) that also consumes
toolsSettings, mapping skipPermissions/bypassPermissions onto
--permission-mode bypass_permissions and allowed/disallowed tools onto the
matching qodercli flags, so the panel config actually reaches spawned
qodercli processes. Covered by qoder-permissions.test.ts.
The Qoder permissions panel was reusing Claude's, so it advertised controls
qodercli does not honor: Bash(cmd:*) sub-command scopes are ignored, and the
quick-add list offered MultiEdit/Task/TodoRead/TodoWrite, which are not Qoder
tools. Users could therefore believe they had granted only `git log` while the
whole Bash tool stayed open.

Give Qoder its own panel with bare tool names grouped by purpose, copy that
states the measured semantics (allow list only skips prompts, deny beats allow),
and a warning when bypass_permissions can override the block list. Add a
Restrict tool set section mapped to `--tools`, the only flag that actually
removes tools from the session. Since `--tools` is variadic it would swallow the
trailing prompt, so the runtime now emits `--` before it.
…lpers

Provider code had grown four private copies of the same primitives: a cwd
encoder, a file-existence probe, a JSON config reader/writer, and the token
usage arithmetic. Each copy was free to drift from what qodercli writes on
disk, and none of them validated the session id they interpolated into a path.

Adds isSafeSessionId, resolveQoderTranscriptPath, readJsonlEntries,
aggregateQoderTranscriptTokenUsage and a shared fileExists so the qoder
runtime, models provider, sessions provider, synchronizer and token usage
service can converge on one derivation in the follow-up commits.
…sion

Probing qodercli 1.1.13 shows preview models print as
`Peach-07-17-DogFooding (qwen3.8-max-preview)` while only the leading token is
a usable id — passing the printed string back as `-m` fails with
`Invalid model`. The parser accepted any line that was not the MODEL header or
a JSON fragment, so it offered that unusable string as a selectable model, and
any future banner or warning row would have become one too.

Rows are now matched against the two shapes the CLI actually emits (bare id, or
id plus parenthesized alias); anything else is dropped with a warning.
…tring results

Three defects found by probing qodercli 1.1.13 against the provider code:

Session ids arrived from WebSocket and HTTP payloads and were interpolated
straight into transcript paths, so an id containing `../` could read any .jsonl
on the host. All three call sites now go through the shared resolver, which
whitelists the id and owns the cwd encoding; the containment check it replaces
compared a path against the directory it was joined from and could never fail.

The permission block can end with the variadic `--tools`, which keeps consuming
bare arguments, yet `--attachment` pairs were emitted after it. Attachments now
precede that block so nothing but `--` can follow it.

The CLI reports its final answer as `"result": "OK"`, a plain string, but only
the Claude-style content array was handled, so the answer was dropped while the
run still reported success.

Also declares supportsTokenUsage false: every token field in a Qoder transcript
is 0 because the CLI measures spend as credits plus a context usage ratio.
Transcripts grow without bound, yet three readers pulled whole files into
memory: the runtime after every run, the models provider on every active-model
query, and the synchronizer three separate times per session while resolving a
title. All of them now stream rows, and the synchronizer collects its title
candidates in a single pass.

The sessions provider also stops scanning every directory under
~/.qoder/projects to locate a transcript — it derives the path the same way the
models provider does, which removes a magic 2000-iteration cap that could
silently give up.

getSupportedModels spawns `qodercli --list-models` with a 20s timeout, and
getCurrentActiveModel falls back to it whenever a session has no recorded model,
bypassing providerModelsService's on-disk cache; successful catalogs are now
memoized for five minutes.

Drops the pre-upsert rename in synchronize(): createSession already writes the
resolved name over custom_name in both of its branches.
…ations

The provider shipped several stubs that could never do anything: two exports
with no consumer, a permission options field that was always an empty object,
a meta-row set and an attachment flag referenced only by `void` statements, and
a local variable named `process` shadowing the global inside the abort path.

Images were destructured and then discarded without a word to the user, so a
Qoder run just looked like it ignored the picture; the runtime now says how many
were dropped and why.

The Skills tab was hidden for Qoder even though QoderSkillsProvider is
registered and reads .qoder, .claude and .agents skill folders, making a working
backend facet unreachable. It is now shown, with the install path the provider
actually scans.

Removes the plan permission mode from the frontend fallback: the capability
matrix excludes it and the runtime emits no flag for it, so choosing plan
silently did nothing.

Adds the four qoder keys the zh-CN and zh-TW bundles were missing so the
Chinese UI stops falling back to English.
…sync title priority

Extract buildQoderArgs as a pure exported function from the qoder runtime so
the ordering invariant — attachments before the variadic --tools, then the
-- separator, then the prompt — can be pinned without spawning the CLI. Add
coverage for QoderSessionsProvider.normalizeMessage (every event type incl.
result string form), fetchHistory pagination, QoderSessionSynchronizer title
priority and sidechain filtering, and the qoder branch of the provider
token-usage service. Update providers/README.md with qoder across all six
documentation locations.
- Pass images as --attachment flags to the Qoder CLI.
- Append <images_input> tag to the prompt before <files_input>.
- Parse <images_input> back out when normalizing session history.
- Enable supportsImages for the qoder provider.
- Deduplicate message ids for multi-part result content
- Avoid unconditional qoder model preload for every provider
- Add shell resume fallback for qoder on unix/win32
- Buffer stderr and only emit as error on non-zero exit
- Memoize active model transcript scans with 30s TTL
- Normalize transcript model names against supported catalog
@kanmars
kanmars marked this pull request as draft August 5, 2026 10:26
@kanmars
kanmars marked this pull request as ready for review August 5, 2026 10:26
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Qoder is added as a supported provider across backend runtime services, sessions, models, MCP, authentication, agent routes, chat, settings, onboarding, localization, documentation, and tests.

Changes

Qoder provider integration

Layer / File(s) Summary
Provider foundation
server/shared/*, server/modules/providers/list/qoder/*, server/modules/providers/services/*
Adds Qoder authentication, models, permissions, MCP, skills, sessions, synchronization, capabilities, transcript utilities, and registry wiring.
Runtime and agent execution
server/index.ts, server/modules/agent/*, server/modules/websocket/services/*
Registers the Qoder runtime, validates agent requests, executes qodercli, handles process lifecycle, and supports shell resume commands.
Session history and synchronization
server/modules/providers/list/qoder/qoder-sessions.provider.ts, server/modules/providers/list/qoder/qoder-session-synchronizer.provider.ts, server/modules/providers/tests/*
Normalizes Qoder transcript events, synchronizes sessions, resolves titles, aggregates usage, and validates pagination and transcript behavior.
Chat and provider surface
src/components/chat/*, src/components/provider-auth/*, src/components/mcp/*, src/components/onboarding/*, src/components/skills/*, src/components/llm-logo-provider/*
Adds Qoder model state, labels, logo, authentication flow, MCP metadata, onboarding, skills, provider selection, and translations.
Settings and validation
src/components/settings/*, public/api-docs.html, server/modules/providers/README.md, src/i18n/locales/*
Adds persisted Qoder permissions, settings controls, API documentation, provider documentation, localization, and supporting validation updates.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ChatInterface
  participant AgentRoutes
  participant qoderRuntime
  participant qodercli
  User->>ChatInterface: Select Qoder model and permissions
  ChatInterface->>AgentRoutes: Submit Qoder agent request
  AgentRoutes->>qoderRuntime: Pass project, session, model, effort, and permissions
  qoderRuntime->>qodercli: Spawn Qoder command
  qodercli-->>qoderRuntime: Stream JSONL or text output
  qoderRuntime-->>ChatInterface: Forward normalized messages and session events
Loading

Possibly related PRs

Suggested reviewers: viper151

Poem

A rabbit checks each model row,
Then starts the Qoder stream to flow.
Sessions bloom and tools align,
Settings save each guarded line.
The CLI hops through every sign.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the addition of a native Qoder provider backed by the qodercli binary, which is the main change in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (3)
server/modules/providers/list/qoder/qoder-models.provider.ts (1)

255-268: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the two resolveQoderJsonlPath implementations.

This file and server/modules/providers/list/qoder/qoder-sessions.provider.ts (lines 34-48) both resolve the Qoder transcript path, but they disagree on two points:

  • This version trusts jsonl_path without checking that the file still exists. The sessions version guards with fs.existsSync and falls back to resolveQoderTranscriptPath.
  • This version looks the row up by provider_session_id first, then by id. The sessions version looks it up by app session id only.

If a recorded jsonl_path becomes stale (project moved, transcript pruned), getCurrentActiveModel reads a missing file and reports the default model, while fetchHistory recovers through the canonical resolver. Extract one shared resolver so both facets behave the same.

♻️ Proposed fix to add the existence guard
 const resolveQoderJsonlPath = (providerSessionId: string, projectPath?: string): string | null => {
   // Prefer the transcript path recorded by the synchronizer so cwd encoding
   // stays consistent with what the CLI actually wrote on disk.
   const storedPath = sessionsDb.getSessionByProviderSessionId(providerSessionId)?.jsonl_path
     ?? sessionsDb.getSessionById(providerSessionId)?.jsonl_path;
-  if (storedPath) {
+  if (storedPath && fs.existsSync(storedPath)) {
     return storedPath;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/qoder/qoder-models.provider.ts` around lines
255 - 268, Extract the shared Qoder transcript-path resolver used by
resolveQoderJsonlPath in this file and qoder-sessions.provider.ts, aligning both
callers to look up sessions by app session id only. Validate any stored
jsonl_path with fs.existsSync before returning it, and otherwise fall back to
resolveQoderTranscriptPath so getCurrentActiveModel and fetchHistory recover
identically from stale or missing transcripts.
server/modules/providers/list/qoder/qoder-sessions.provider.ts (1)

354-385: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Merge token-usage accumulation into the normalization pass.

fetchHistory already buffers the transcript to compute total and sort entries, so keeping the tail-paging pattern is acceptable. Eliminate the separate token-usage pass by accumulating usage during normalization instead of calling aggregateQoderTranscriptTokenUsage(rawMessages) on the retained buffer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/qoder/qoder-sessions.provider.ts` around lines
354 - 385, The fetchHistory normalization flow should accumulate token usage
while iterating through rawMessages, eliminating the separate
aggregateQoderTranscriptTokenUsage(rawMessages) pass over the retained buffer.
Update the normalization loop to collect the same cumulative usage result, then
preserve the existing tokenUsage behavior and tail-pagination logic.
server/modules/providers/tests/qoder-args.test.ts (1)

6-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for images.

baseOptions omits images, and no test passes it. buildQoderArgs handles images separately from files: it emits --attachment for each image descriptor and appends the <images_input> tag through appendImagesInputTag. Image attachment support is new in this PR, so the image branch has no coverage here. Add a case that mirrors the files test.

💚 Proposed test case
+test('buildQoderArgs: images produce --attachment and an images_input tag', () => {
+  const args = buildQoderArgs({
+    ...baseOptions,
+    command: undefined,
+    images: ['/path/to/image.png'],
+  });
+
+  const attachmentIndex = args.indexOf('--attachment');
+  assert.ok(attachmentIndex > -1, 'expected --attachment');
+  assert.equal(args[attachmentIndex + 1], '/path/to/image.png');
+
+  const lastArg = args.at(-1);
+  assert.ok(lastArg.includes('<images_input>'), 'prompt must contain images_input tag');
+  assert.ok(lastArg.includes('/path/to/image.png'), 'prompt must contain the image path');
+});
+
 test('buildQoderArgs: no prompt and no files means no -- separator and no prompt arg', () => {

Also applies to: 107-118

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/tests/qoder-args.test.ts` around lines 6 - 15, Add
image coverage in the qoder argument tests: extend baseOptions with images and
add a test mirroring the existing files case that passes image descriptors to
buildQoderArgs. Assert each image produces a --attachment argument and that the
generated prompt includes the <images_input> tag via appendImagesInputTag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/modules/providers/list/qoder/qoder-auth.provider.ts`:
- Around line 22-25: Update QoderAuthProvider.checkInstalled to be asynchronous
and replace spawn.sync with the non-blocking child-process API for qodercli
--version; enforce the five-second limit by calling child.kill() when the
timeout elapses, then resolve based on successful exit status and error state.
Propagate the async return through each caller of checkInstalled.

In `@server/modules/providers/list/qoder/qoder-runtime.provider.js`:
- Around line 354-381: Update the close handler around qoderProcess.aborted to
handle aborted runs before the code === 127 || code === null failure checks:
skip installation/error notifications and notifyTerminalState failure reporting,
then reject with an abort-specific error. Preserve the existing completion
behavior and normal exit/failure handling for non-aborted processes.

In `@server/modules/providers/list/qoder/qoder-sessions.provider.ts`:
- Around line 178-186: Update the attachment-handling flow in the session
mapping callback to track image and file attachments with separate flags instead
of the shared filesAttached flag. Guard parsedImages and parsedFiles
independently so later text parts still include whichever attachment type has
not yet been emitted, and update each flag only when its corresponding
attachments are present.

In `@server/shared/utils.ts`:
- Around line 1041-1044: Update the token aggregation logic around inputTokens,
outputTokens, cacheReadTokens, and cacheCreationTokens to validate each usage
field before addition. Convert values safely, and add them only when the result
is finite and non-negative; otherwise skip that field so malformed usage cannot
make totals NaN.
- Around line 990-991: Normalize the resolved cwd to forward-slash separators
before passing it to encodeQoderCwd in the transcript path construction. Update
the path.resolve(cwd) value used by encodeQoderCwd so Windows backslashes are
converted first, while preserving the existing path.join behavior with
projectsDir and sessionId.

In `@src/components/mcp/constants.ts`:
- Around line 19-24: Update the Qoder MCP configuration flow associated with
MCP_SUPPORTED_TRANSPORTS to reject or sanitize credential-bearing HTTP
destinations before writing settings.json or .mcp.json. Require HTTPS whenever
headers or bearer tokens are present, or strip those credentials for plaintext
HTTP, while preserving existing behavior for non-credentialed and HTTPS servers.

In
`@src/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsx`:
- Line 34: Update the defaultValue selection in AgentCategoryTabsSection to use
the provider-specific translation key for shared skills when selectedAgent is
opencode or qoder, while retaining tabs.skills for other agents. Add or reuse
the corresponding tabs.sharedSkills and tabs.skills translation keys across the
supported locales.

In `@src/i18n/locales/en/settings.json`:
- Line 479: Add the missing project scope label to the mcpServers.scope map in
src/i18n/locales/en/settings.json at lines 479-479 and
src/i18n/locales/zh-CN/settings.json at lines 479-479, using the appropriate
localized text so non-Qoder project MCP servers resolve their scope labels
correctly.

---

Nitpick comments:
In `@server/modules/providers/list/qoder/qoder-models.provider.ts`:
- Around line 255-268: Extract the shared Qoder transcript-path resolver used by
resolveQoderJsonlPath in this file and qoder-sessions.provider.ts, aligning both
callers to look up sessions by app session id only. Validate any stored
jsonl_path with fs.existsSync before returning it, and otherwise fall back to
resolveQoderTranscriptPath so getCurrentActiveModel and fetchHistory recover
identically from stale or missing transcripts.

In `@server/modules/providers/list/qoder/qoder-sessions.provider.ts`:
- Around line 354-385: The fetchHistory normalization flow should accumulate
token usage while iterating through rawMessages, eliminating the separate
aggregateQoderTranscriptTokenUsage(rawMessages) pass over the retained buffer.
Update the normalization loop to collect the same cumulative usage result, then
preserve the existing tokenUsage behavior and tail-pagination logic.

In `@server/modules/providers/tests/qoder-args.test.ts`:
- Around line 6-15: Add image coverage in the qoder argument tests: extend
baseOptions with images and add a test mirroring the existing files case that
passes image descriptors to buildQoderArgs. Assert each image produces a
--attachment argument and that the generated prompt includes the <images_input>
tag via appendImagesInputTag.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ee389ffa-9438-440c-8dd1-ed658659397a

📥 Commits

Reviewing files that changed from the base of the PR and between f0dca2d and 6cbf378.

⛔ Files ignored due to path filters (1)
  • src/assets/qoder.png is excluded by !**/*.png
📒 Files selected for processing (68)
  • public/api-docs.html
  • server/index.ts
  • server/modules/agent/agent.module.ts
  • server/modules/agent/agent.routes.ts
  • server/modules/agent/tests/agent.routes.test.ts
  • server/modules/commands/commands.routes.ts
  • server/modules/providers/README.md
  • server/modules/providers/list/opencode/opencode-mcp.provider.ts
  • server/modules/providers/list/qoder/qoder-auth.provider.ts
  • server/modules/providers/list/qoder/qoder-mcp.provider.ts
  • server/modules/providers/list/qoder/qoder-models.provider.ts
  • server/modules/providers/list/qoder/qoder-permissions.provider.ts
  • server/modules/providers/list/qoder/qoder-runtime.provider.js
  • server/modules/providers/list/qoder/qoder-session-synchronizer.provider.ts
  • server/modules/providers/list/qoder/qoder-sessions.provider.ts
  • server/modules/providers/list/qoder/qoder-skills.provider.ts
  • server/modules/providers/list/qoder/qoder.provider.ts
  • server/modules/providers/provider.registry.ts
  • server/modules/providers/provider.routes.ts
  • server/modules/providers/services/provider-capabilities.service.ts
  • server/modules/providers/services/provider-token-usage.service.ts
  • server/modules/providers/services/session-synchronizer.service.ts
  • server/modules/providers/services/sessions-watcher.service.ts
  • server/modules/providers/tests/mcp.test.ts
  • server/modules/providers/tests/provider-runtime.service.test.ts
  • server/modules/providers/tests/provider-token-usage.service.test.ts
  • server/modules/providers/tests/qoder-args.test.ts
  • server/modules/providers/tests/qoder-models.test.ts
  • server/modules/providers/tests/qoder-permissions.test.ts
  • server/modules/providers/tests/qoder-session-synchronizer.test.ts
  • server/modules/providers/tests/qoder-sessions.test.ts
  • server/modules/websocket/services/shell-websocket.service.ts
  • server/shared/types.ts
  • server/shared/utils.ts
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/hooks/useChatProviderState.ts
  • src/components/chat/view/ChatInterface.tsx
  • src/components/chat/view/subcomponents/ChatMessagesPane.tsx
  • src/components/chat/view/subcomponents/CommandResultModal.tsx
  • src/components/chat/view/subcomponents/MessageComponent.tsx
  • src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
  • src/components/llm-logo-provider/QoderLogo.tsx
  • src/components/llm-logo-provider/SessionProviderLogo.tsx
  • src/components/mcp/constants.ts
  • src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx
  • src/components/provider-auth/types.ts
  • src/components/provider-auth/view/ProviderLoginModal.tsx
  • src/components/settings/constants/constants.ts
  • src/components/settings/hooks/useSettingsController.ts
  • src/components/settings/types/types.ts
  • src/components/settings/view/Settings.tsx
  • src/components/settings/view/tabs/agents-settings/AgentListItem.tsx
  • src/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsx
  • src/components/settings/view/tabs/agents-settings/sections/AgentCategoryContentSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/AgentSelectorSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/content/AccountContent.tsx
  • src/components/settings/view/tabs/agents-settings/sections/content/PermissionsContent.tsx
  • src/components/settings/view/tabs/agents-settings/types.ts
  • src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx
  • src/components/skills/view/ProviderSkills.tsx
  • src/i18n/locales/en/chat.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/zh-CN/chat.json
  • src/i18n/locales/zh-CN/settings.json
  • src/i18n/locales/zh-TW/chat.json
  • src/i18n/locales/zh-TW/settings.json
  • src/types/app.ts

Comment thread server/modules/providers/list/qoder/qoder-auth.provider.ts Outdated
Comment thread server/modules/providers/list/qoder/qoder-runtime.provider.js
Comment thread server/modules/providers/list/qoder/qoder-sessions.provider.ts Outdated
Comment thread server/shared/utils.ts Outdated
Comment thread server/shared/utils.ts Outdated
Comment thread src/components/mcp/constants.ts Outdated
Comment thread src/i18n/locales/en/settings.json
baolong.bl added 2 commits August 6, 2026 17:53
- abort: resolve instead of reject when process.aborted to prevent
  false "not installed" error messages on user-initiated session stops
- sessions: split filesAttached into imagesAttached + filesAttached so
  image and file attachment suppression are tracked independently
- utils: guard token aggregation with a safeCount helper that rejects
  NaN and negative values to prevent corrupted cumulative totals
- utils: normalize Windows backslashes before encodeQoderCwd so drive
  letters are encoded correctly on Windows paths
- auth: convert checkInstalled to async spawn to avoid blocking the
  event loop for up to 5 s on each provider status check
- mcp/constants: restrict qoder to stdio transport only to prevent
  bearer tokens from being written to plaintext HTTP configs
- Skip failure notification on user-aborted runs
- Use provider-specific sharedSkills i18n key instead of hardcoded defaultValue
- Add mcpServers.scope.project labels to en/zh-CN locales
- Align models resolver to look up by app session id and verify stored path exists
- Merge token usage accumulation into the fetchHistory normalization pass
- Add image attachment coverage to qoder-args tests

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/modules/providers/tests/qoder-args.test.ts (1)

138-147: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover empty values explicitly.

This test passes undefined for command and files. It does not cover command: '' or files: [], although the test name describes empty inputs. Add those cases to verify that empty values also produce no separator and no prompt argument.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/tests/qoder-args.test.ts` around lines 138 - 147,
Expand the buildQoderArgs empty-input test to cover command: '' and files: [] in
addition to undefined values. Verify each empty-value case produces no '--'
separator and leaves '/home/user/project' as the final argument, preserving the
existing expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/i18n/locales/es/settings.json`:
- Line 8: Update the sharedSkills label in the Spanish locale from the English
text to a Spanish translation such as “Habilidades compartidas”.

---

Nitpick comments:
In `@server/modules/providers/tests/qoder-args.test.ts`:
- Around line 138-147: Expand the buildQoderArgs empty-input test to cover
command: '' and files: [] in addition to undefined values. Verify each
empty-value case produces no '--' separator and leaves '/home/user/project' as
the final argument, preserving the existing expectations.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0074057a-904d-4aaa-8388-871049fba222

📥 Commits

Reviewing files that changed from the base of the PR and between 5af52b1 and 9a79098.

📒 Files selected for processing (9)
  • server/modules/providers/list/qoder/qoder-models.provider.ts
  • server/modules/providers/list/qoder/qoder-runtime.provider.js
  • server/modules/providers/list/qoder/qoder-sessions.provider.ts
  • server/modules/providers/tests/qoder-args.test.ts
  • src/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsx
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/es/settings.json
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/zh-CN/settings.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsx
  • server/modules/providers/list/qoder/qoder-sessions.provider.ts
  • server/modules/providers/list/qoder/qoder-models.provider.ts
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/zh-CN/settings.json
  • server/modules/providers/list/qoder/qoder-runtime.provider.js

"permissions": "Permisos",
"mcpServers": "Servidores MCP",
"skills": "Skills",
"sharedSkills": "Shared Skills",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Translate the sharedSkills label in the Spanish locale.

Line 8 keeps the English text "Shared Skills". Use a Spanish label, such as "Habilidades compartidas", so the shared-skills tab is localized.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/i18n/locales/es/settings.json` at line 8, Update the sharedSkills label
in the Spanish locale from the English text to a Spanish translation such as
“Habilidades compartidas”.

@kanmars

kanmars commented Aug 6, 2026

Copy link
Copy Markdown
Author

Hi @viper151,

This PR is ready for review. It adds first-class Qoder support to ClaudeCodeUI by integrating the local qodercli binary into the existing provider model.

What this branch adds (vs. main)

Backend provider implementation (server/modules/providers/list/qoder/)

  • New Qoder provider wrapper registering all facets with AbstractProvider.
  • runtime: spawns qodercli with deterministic argv ordering, safe cwd encoding, session id validation, and image/file attachment support.
  • models: discovers available models via qodercli --list-models with memoized catalog and fallback metadata.
  • auth: reports install/auth status and supports token-based login.
  • mcp: reads/writes Qoder-native MCP config from ~/.qoder/settings.json (user) and .mcp.json (project).
  • skills: discovers SKILL.md files from Qoder/Claude/Agents skill roots and emits /skill-name commands.
  • sessions: normalizes live events and fetches session history with unique message ids, pagination, and title priority.
  • sessionSynchronizer: scans ~/.qoder/projects/**/*.jsonl and upserts sessions into sessionsDb.

Shared infrastructure

  • Added Qoder helpers in server/shared/utils.ts for transcript paths, jsonl handling, cwd encoding, and token aggregation.
  • Updated capabilities, token usage, sessions watcher, and synchronizer services to include Qoder.

Module wiring

  • Registered qoder in provider registry/routes and LLMProvider types.
  • Wired into agent routes, command routes, shell websocket, and API docs.

Frontend

  • Added Qoder brand icon and QoderLogo component.
  • Updated provider selection, session logo, chat provider state, and login modal.
  • Added Qoder permissions UI, MCP constants, settings hooks, and onboarding flow.
  • Updated i18n strings for English, Simplified Chinese, Traditional Chinese, Spanish, and Korean.

Tests

  • qoder-args, qoder-models, qoder-permissions, qoder-sessions, qoder-session-synchronizer.

Code review status

All CodeRabbit comments have been addressed:

  • Fixed in 5af52b1: async CLI install check, separate image/file attachment flags, safe token aggregation, Windows cwd normalization, and restricting Qoder MCP to stdio.
  • Fixed in 9a79098: refined abort-run handling, aligned transcript-path resolver between models/sessions, merged token-usage accumulation into the normalization pass, added image-attachment test coverage, switched skills tab to provider-specific i18n key, and added the missing project MCP scope label.

All Qoder-related tests pass locally (47/47).

Could you please review or assign it to the right owner? Thanks!

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