Skip to content

fix: rtk status - #1541

Merged
zerob13 merged 5 commits into
devfrom
fix/rtk-status-check
Apr 27, 2026
Merged

fix: rtk status#1541
zerob13 merged 5 commits into
devfrom
fix/rtk-status-check

Conversation

@zerob13

@zerob13 zerob13 commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator
  • rtk status
  • provider model enable/disable
  • internal knowldge base

Summary by CodeRabbit

  • New Features

    • Builtin knowledge config management: add/get/set routes, schema, and UI settings for managing built-in knowledge sources.
  • Improvements

    • Runtime health checks simplified to version-based probes.
    • Model catalog handling improved with better type inference and unified model ID deduplication.
    • In-memory knowledge services now reflect current enabled configs dynamically.
  • Tests

    • Expanded test coverage across knowledge configs, servers, routes, and runtime checks.

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Centralizes builtin knowledge config storage and migration via ConfigPresenter and a new config API, updates MCP in-memory servers to read enabled configs at runtime, adds concurrency-safe KnowledgeStorePresenter initialization, and simplifies RTK health verification to version-only probes (failureStage now 'version').

Changes

Cohort / File(s) Summary
Runtime health checks
src/main/lib/agentRuntime/rtkRuntimeService.ts, test/main/lib/agentRuntime/rtkRuntimeService.test.ts
Removed rewrite/rg/file-smoke/gain health probes; runtime health now uses only <candidate>.command --version and injected rtk --version. Tests updated to assert version-only probes and updated failureStage to 'version'.
Builtin knowledge routes & contracts
src/shared/contracts/domainSchemas.ts, src/shared/contracts/routes/config.routes.ts, src/shared/contracts/routes.ts
Added BuiltinKnowledgeConfigSchema and two route contracts: config.getKnowledgeConfigs and config.setKnowledgeConfigs, and registered them in the route catalog.
Config presenter & migration
src/main/presenter/configPresenter/index.ts, src/main/presenter/configPresenter/mcpConfHelper.ts
Introduced migrateBuiltinKnowledgeConfigsFromEnv, centralized provider-DB model-type inference, persist migrated configs, and emit CONFIG_CHANGED after updates.
Renderer client & settings UI
src/renderer/api/ConfigClient.ts, src/renderer/settings/components/BuiltinKnowledgeSettings.vue
Added getKnowledgeConfigs/setKnowledgeConfigs client methods; settings component now uses the client API (loads on mount) instead of direct MCP store manipulation.
MCP in-memory servers & builder
src/main/presenter/mcpPresenter/inMemoryServers/builder.ts, src/main/presenter/mcpPresenter/inMemoryServers/builtinKnowledgeServer.ts
Builder stopped injecting env-provided configs; BuiltinKnowledgeServer now fetches enabled configs at request-time via configPresenter.getKnowledgeConfigs() and generates tools/results from that runtime set.
KnowledgePresenter concurrency & lifecycle
src/main/presenter/knowledgePresenter/index.ts
Added init-task tracking to coalesce concurrent KnowledgeStorePresenter creation, made createStorePresenter idempotent and concurrency-safe, and reconciles CONFIG_CHANGED using snapshots/diffs.
Routes & model handling
src/main/routes/config/configRouteHandler.ts, src/main/routes/models/modelRouteHandler.ts
Added dispatch branches for get/set knowledge configs; model route now includes dbProviderModels when building model ID lists for batch status.
MCP env coercion & server input typing
src/main/presenter/mcpPresenter/mcpClient.ts, src/main/presenter/mcpPresenter/inMemoryServers/*.ts
Loosened server env typing to unknown; coercion of env values to strings when applying to stdio/env and when building per-server configs; multiple servers now defensively parse unknown env entries.
Tests: new and updated coverage
test/** (many files; see diff)
Added/updated tests for migration helper, builtin knowledge server, config route dispatch, renderer client behavior, KnowledgePresenter concurrency, and RTK version-only health checks. Numerous test files updated/added.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Vue as BuiltinKnowledgeSettings
    participant ConfigClient
    participant Route as ConfigRouteHandler
    participant ConfigPresenter
    participant McpHelper as McpConfHelper
    participant MCP as MCP Store
    participant EventBus
    participant KnowledgeP as KnowledgePresenter

    User->>Vue: Edit/save builtin config
    Vue->>ConfigClient: setKnowledgeConfigs(nextConfigs)
    ConfigClient->>Route: POST config.setKnowledgeConfigs
    Route->>ConfigPresenter: setKnowledgeConfigs(configs)
    ConfigPresenter->>McpHelper: migrateBuiltinKnowledgeConfigsFromEnv()
    McpHelper->>MCP: read mcpServers.builtinKnowledge.env
    McpHelper->>MCP: persist cleared env
    McpHelper-->>ConfigPresenter: migrated configs
    ConfigPresenter->>MCP: persist configs
    ConfigPresenter->>EventBus: emit CONFIG_CHANGED
    EventBus->>KnowledgeP: CONFIG_CHANGED
    KnowledgeP->>ConfigPresenter: getKnowledgeConfigs()
    KnowledgeP->>KnowledgeP: diff & apply updates
    ConfigPresenter-->>Route: return updated configs
    Route-->>ConfigClient: response
    ConfigClient-->>Vue: success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through envs and tiny config stacks,
I nudged old lists into safe, new tracks.
Tools ask the presenter when they call,
RTK states its version — that's all.
A little hop, a nibble, and a happy clap!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'fix: rtk status' is only partially related to the changeset; it addresses RTK runtime health verification simplification but fails to capture the substantial parallel work on knowledge config migration, provider model type inference, and knowledge presenter refactoring. Expand the title to reflect the primary scope: e.g., 'fix: rtk health check and refactor knowledge config management' or break into multiple focused commits with more descriptive titles.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rtk-status-check

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 and usage tips.

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/presenter/knowledgePresenter/index.ts (1)

140-157: ⚠️ Potential issue | 🟡 Minor

Rejected in-flight init will throw out of update().

await this.storePresenterInitTasks.get(config.id) re-throws if the original createStorePresenter rejected (e.g., DuckDB open failure). Since update is called from Promise.all(diffs.updated.map(...)) in syncKnowledgeConfigChanges, a single failed init poisons the whole reconciliation batch. Consider guarding the await so a previously-failed init doesn't break unrelated updates.

🛡️ Possible guard
-      const initializingRag = await this.storePresenterInitTasks.get(config.id)
-      if (initializingRag) {
-        initializingRag.updateConfig(config)
-      }
+      const initTask = this.storePresenterInitTasks.get(config.id)
+      if (initTask) {
+        try {
+          const initializingRag = await initTask
+          initializingRag.updateConfig(config)
+        } catch (error) {
+          console.warn(`[RAG] Skipping update for in-flight failed init ${config.id}:`, error)
+        }
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/knowledgePresenter/index.ts` around lines 140 - 157, The
update method can throw when awaiting a previously-rejected init promise from
storePresenterInitTasks which poisons the whole reconciliation; change the await
of this.storePresenterInitTasks.get(config.id) inside update to be guarded so a
rejected init does not rethrow (for example, retrieve the promise, await it with
a try/catch or use .catch(() => undefined) and proceed only if it resolved to an
object), keep the existing behavior of calling
initializingRag.updateConfig(config) when the init succeeded, and leave
closeStorePresenterIfExists/config-disabled path unchanged; refer to update,
storePresenterInitTasks, getStorePresenter and createStorePresenter to locate
the logic to modify.
🧹 Nitpick comments (9)
src/main/lib/agentRuntime/rtkRuntimeService.ts (1)

600-611: Skip the redundant rtk --version probe for the system candidate.

When candidate.source === 'system', candidate.command is 'rtk', so the first --version call (lines 589–598) and this second call (lines 600–611) execute the exact same process with the same env. The current test case tries system RTK when bundled RTK version check fails already documents this with the sequence ['rtk', ['--version']], ['rtk', ['--version']] after the bundled probe.

The PATH-resolvability assertion is only meaningful for the bundled candidate (i.e., to confirm prependBundledRuntimeToEnv actually surfaced rtk on PATH). Consider short-circuiting for the system candidate.

♻️ Proposed refactor
-    const resolvedRtk = await this.runCommandImpl('rtk', ['--version'], {
-      env: baseEnv,
-      timeoutMs: RTK_HEALTH_TIMEOUT_MS
-    })
-    if (resolvedRtk.code !== 0) {
-      throw new RtkHealthCheckError(
-        'version',
-        resolvedRtk.stderr.trim() ||
-          resolvedRtk.stdout.trim() ||
-          'rtk is not resolvable via injected PATH'
-      )
-    }
+    if (candidate.source === 'bundled') {
+      const resolvedRtk = await this.runCommandImpl('rtk', ['--version'], {
+        env: baseEnv,
+        timeoutMs: RTK_HEALTH_TIMEOUT_MS
+      })
+      if (resolvedRtk.code !== 0) {
+        throw new RtkHealthCheckError(
+          'version',
+          resolvedRtk.stderr.trim() ||
+            resolvedRtk.stdout.trim() ||
+            'rtk is not resolvable via injected PATH'
+        )
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/lib/agentRuntime/rtkRuntimeService.ts` around lines 600 - 611, The
second redundant probe calling this.runCommandImpl('rtk', ['--version'], ...)
should be skipped when the runtime candidate is the system one; detect
candidate.source === 'system' (and/or candidate.command === 'rtk') and
short-circuit before calling runCommandImpl a second time so you only perform
the PATH-resolvability check for bundled candidates (where
prependBundledRuntimeToEnv was applied). Ensure the existing RtkHealthCheckError
logic remains for failures of the bundled check and do not change error
shape—only avoid invoking runCommandImpl for system candidates.
test/main/lib/agentRuntime/rtkRuntimeService.test.ts (1)

69-118: Helper trio is clean and tightens regression coverage.

createHealthCheckRunCommand defaults make the happy-path tests very readable, and expectNoHealthCommandProbes is a useful guardrail against accidentally re-introducing rewrite/read/gain/find/ls/rg probes during health checks. One nit: createHealthCheckService differs from createService only in the replaceWithRuntimeCommand mock; consider parameterizing createService instead to drop the duplication. Optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/main/lib/agentRuntime/rtkRuntimeService.test.ts` around lines 69 - 118,
The helper duplication can be removed by parameterizing the existing
createService factory so tests reuse it instead of a separate
createHealthCheckService: update createService to accept an optional overrides
object (e.g., replaceWithRuntimeCommand, getShellEnvironment, runCommand,
getPath) and move the unique replaceWithRuntimeCommand mock currently in
createHealthCheckService into that overrides parameter; then replace
createHealthCheckService usages with calls to createService({
replaceWithRuntimeCommand: vi.fn((command)=> command === 'rtk' ?
'/runtime/rtk/rtk.exe' : command), runCommand }) so tests keep behavior while
eliminating the duplicate function. Ensure symbols referenced are createService,
createHealthCheckService, and replaceWithRuntimeCommand when making the change.
src/renderer/settings/components/BuiltinKnowledgeSettings.vue (1)

936-942: Surface load failures to the user.

loadBuiltinConfig silently swallows errors with only a console.error. If the IPC call fails (or returns a validation error), the panel will appear empty with no indication to the user, and any subsequent save will overwrite the (unloaded) configs with whatever is currently in builtinConfigs.value (i.e. []). Consider toasting on failure and/or guarding saves until at least one successful load has happened.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/settings/components/BuiltinKnowledgeSettings.vue` around lines
936 - 942, loadBuiltinConfig currently swallows errors with console.error, so
surface failures to the user and prevent accidental overwrites: when catching
errors from configClient.getKnowledgeConfigs() (in loadBuiltinConfig), call the
app's user-facing notification/toast API to show the failure (include the error
message) and set a boolean flag like
builtinConfigsLoaded/hasLoadedBuiltinConfigs to false; on successful load set it
to true and populate builtinConfigs.value; update the save path (e.g., the save
handler or saveBuiltinConfig and any save button disabled state) to guard
against saving when builtinConfigsLoaded is false so saves are disabled or
rejected until a successful load completes.
src/shared/contracts/domainSchemas.ts (1)

21-33: Consider tightening numeric fields against NaN / negative values.

dimensions, chunkSize, chunkOverlap, and fragmentsNumber are declared as bare z.number(), which in Zod accepts NaN and negative values. The renderer initializes editingBuiltinConfig.dimensions to NaN and serializing NaN over IPC turns into null, which would then fail validation in confusing ways. Adding .int().positive() (or at minimum .finite()/.nonnegative() where appropriate) would catch invalid configs at the contract boundary instead of letting them propagate.

Does Zod v4 z.number() accept NaN by default?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/contracts/domainSchemas.ts` around lines 21 - 33,
BuiltinKnowledgeConfigSchema uses bare z.number() which accepts NaN and
negatives (Zod v4 z.number() does accept NaN by default), so update the schema
to validate numeric fields: change dimensions and fragmentsNumber to
z.number().int().positive(), change chunkSize and chunkOverlap to
z.number().finite().nonnegative().optional() (or .int().nonnegative() if
integers are expected), and keep any optional chaining (.optional()) as needed
so invalid NaN/negative values are rejected at the contract boundary; apply
these changes on the BuiltinKnowledgeConfigSchema definition to prevent NaN/null
propagation.
src/main/presenter/knowledgePresenter/index.ts (1)

80-85: Listener fires for every MCP config change, not only knowledge-config changes.

MCP_EVENTS.CONFIG_CHANGED is emitted by setMcpServers, setMcpEnabled, setMcpServerEnabled, batch import, etc. — not just knowledge-config saves. Each of those will now trigger a full configP.getKnowledgeConfigs() (which can itself run the migration helper) and a diff. It's idempotent, but it is a fair amount of work per mcp change. If you add a dedicated knowledge-config event upstream (see the comment on configPresenter.setKnowledgeConfigs), this listener can be narrowed accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/knowledgePresenter/index.ts` around lines 80 - 85, The
current listener on eventBus for MCP_EVENTS.CONFIG_CHANGED calls
syncKnowledgeConfigChanges() for every MCP config change, causing unnecessary
work; update the code to only react to knowledge-config saves by either (a)
subscribing to a new dedicated event emitted by
configPresenter.setKnowledgeConfigs (e.g., MCP_EVENTS.KNOWLEDGE_CONFIG_CHANGED)
or (b) inspecting the CONFIG_CHANGED event payload inside the eventBus.on
callback and only invoking this.syncKnowledgeConfigChanges() when the change
pertains to knowledge configs; locate the handler using
eventBus.on(MCP_EVENTS.CONFIG_CHANGED, ...) and the method
syncKnowledgeConfigChanges to implement the conditional or switch to the new
event name.
src/main/presenter/configPresenter/mcpConfHelper.ts (1)

461-491: Avoid creating a new array when nothing was actually migrated.

When env.configs exists but is empty or fully deduplicated against existingConfigs, the function still returns a brand-new array ([...existingConfigs]) and writes the mcp store. The caller in configPresenter.getKnowledgeConfigs() (lines 2633–2635) uses a reference check (migratedConfigs !== configs) and will trigger a redundant setKnowledgeConfigs (which itself fires off a heavy getMcpServers() and broadcasts MCP_EVENTS.CONFIG_CHANGED). It is a one-time cost (env.configs is removed after the first run) but easy to avoid.

♻️ Proposed fix — only return a new array when something was actually merged
-    const legacyConfigs = Array.isArray(env.configs)
-      ? (env.configs.filter(
-          (config): config is BuiltinKnowledgeConfig =>
-            Boolean(config) &&
-            typeof config === 'object' &&
-            typeof (config as { id?: unknown }).id === 'string'
-        ) as BuiltinKnowledgeConfig[])
-      : []
-    const mergedConfigs = [...existingConfigs]
-    const existingIds = new Set(existingConfigs.map((config) => config.id))
-
-    for (const config of legacyConfigs) {
-      if (!existingIds.has(config.id)) {
-        mergedConfigs.push(config)
-        existingIds.add(config.id)
-      }
-    }
-
-    const migratedEnv = { ...env }
-    delete migratedEnv.configs
-    mcpServers.builtinKnowledge = {
-      ...builtinKnowledge,
-      env: migratedEnv
-    }
-    this.mcpStore.set('mcpServers', mcpServers)
-
-    return mergedConfigs
+    const legacyConfigs = Array.isArray(env.configs)
+      ? (env.configs.filter(
+          (config): config is BuiltinKnowledgeConfig =>
+            Boolean(config) &&
+            typeof config === 'object' &&
+            typeof (config as { id?: unknown }).id === 'string'
+        ) as BuiltinKnowledgeConfig[])
+      : []
+    const existingIds = new Set(existingConfigs.map((config) => config.id))
+    const newlyMerged = legacyConfigs.filter((config) => !existingIds.has(config.id))
+
+    const migratedEnv = { ...env }
+    delete migratedEnv.configs
+    mcpServers.builtinKnowledge = {
+      ...builtinKnowledge,
+      env: migratedEnv
+    }
+    this.mcpStore.set('mcpServers', mcpServers)
+
+    return newlyMerged.length > 0 ? [...existingConfigs, ...newlyMerged] : existingConfigs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/configPresenter/mcpConfHelper.ts` around lines 461 - 491,
The code always creates mergedConfigs = [...existingConfigs] and writes
mcpServers to the store even when no legacy configs are added; change the logic
in the block handling env.configs so you first build legacyConfigs, check
whether any config.id in legacyConfigs is not present in existingIds, and only
if at least one new id exists create mergedConfigs, mutate
mcpServers.builtinKnowledge.env (delete configs) and call
this.mcpStore.set('mcpServers', mcpServers); otherwise skip the store write and
simply return existingConfigs (preserve the original array reference). Ensure
you reference the existing symbols env.configs, existingConfigs, legacyConfigs,
mergedConfigs, existingIds, migratedEnv, mcpServers.builtinKnowledge and
this.mcpStore.set when making the change.
src/main/presenter/configPresenter/index.ts (1)

2641-2653: Heavy/semantically conflated event broadcast on knowledge-config save.

getMcpServers() is not a cheap getter — it normalizes server entries, runs platform-specific filtering, awaits presenter.knowledgePresenter.isSupported(), and may even write back to the store while computing the response. Calling it just to broadcast MCP_EVENTS.CONFIG_CHANGED after a knowledge-config save:

  1. Adds an avoidable async dependency on knowledgePresenter from the config-save path.
  2. Re-emits the full mcp servers payload despite no mcp server config actually changing — listeners that rely on this payload (e.g. anything decorating diffs from MCP_EVENTS.CONFIG_CHANGED) will treat it as an mcp change.
  3. The downstream listener that actually cares (KnowledgePresenter.syncKnowledgeConfigChanges) does not consume the payload — it re-reads configP.getKnowledgeConfigs() on its own.

Consider either (a) emitting a dedicated knowledge-config event, or (b) reading the raw mcp store directly without re-running getMcpServers()'s normalization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/configPresenter/index.ts` around lines 2641 - 2653, The
current setKnowledgeConfigs calls getMcpServers() (an expensive, side-effecting
method) just to broadcast MCP_EVENTS.CONFIG_CHANGED, which wrongly couples
config saves to MCP normalization and re-emits full MCP payloads; change
setKnowledgeConfigs (which calls this.knowledgeConfHelper.setKnowledgeConfigs)
to avoid calling getMcpServers — either emit a new dedicated event (e.g.,
MCP_EVENTS.KNOWLEDGE_CONFIG_CHANGED or a lightweight event) via eventBus.send
with no or minimal payload, or read the raw MCP store values directly (not via
getMcpServers) if listeners need raw data; ensure
KnowledgePresenter.syncKnowledgeConfigChanges remains unchanged (it already
re-reads configP.getKnowledgeConfigs()), and remove the
Promise.all([this.getMcpServers(), this.getMcpEnabled()]) usage to eliminate the
async dependency on knowledgePresenter.isSupported().
test/main/presenter/mcpPresenter/builtinKnowledgeServer.test.ts (1)

1-118: Solid coverage of the dynamic-tool-listing and call dispatch paths.

The vi.hoisted mocks are set up correctly, and the disabled-config filter + suffix renumbering is well covered (knowledge-2 disabled -> knowledge-3 becomes _2).

Two small notes:

  • The source lives in src/main/presenter/mcpPresenter/inMemoryServers/; placing the test at test/main/presenter/mcpPresenter/inMemoryServers/builtinKnowledgeServer.test.ts would mirror the source tree exactly. As per coding guidelines, "Vitest test suites should mirror the source structure under test/main/** and test/renderer/**".
  • Consider replacing the bare Function type at lines 6 and 47/49 with a more precise signature (e.g. (input?: unknown) => Promise<unknown>) — Function is normally flagged by @typescript-eslint/ban-types.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/main/presenter/mcpPresenter/builtinKnowledgeServer.test.ts` around lines
1 - 118, The tests are in the wrong location and use the banned `Function` type:
move the test file so its test tree mirrors the source tree under the same
presenter/inMemoryServers structure (so it lives alongside the source it tests)
and replace the use of bare Function for serverInstances and the mocked
setRequestHandler signature with a precise function type such as (input?:
unknown) => Promise<unknown> (or a synchronous variant if appropriate) to
satisfy `@typescript-eslint/ban-types` and make the handler contract explicit;
update references to serverInstances, mockGetKnowledgeConfigs,
mockSimilarityQuery and the setRequestHandler mock implementation accordingly.
src/main/presenter/mcpPresenter/inMemoryServers/builtinKnowledgeServer.ts (1)

38-67: No tools/list_changed notification is emitted when the tool list changes at runtime.

Since the tool list is derived from the live getEnabledConfigs() snapshot on every ListToolsRequest, MCP clients that cache the initial tool list (most do) won't observe enabled/disabled config flips until they re-list. Consider adding MCP notifications/tools/list_changed emission when underlying knowledge configs change to keep active clients in sync with runtime state.

This can be addressed in a follow-up as part of the broader knowledge-config event flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/builtinKnowledgeServer.ts`
around lines 38 - 67, The server never notifies clients when the runtime tool
list changes; update builtinKnowledgeServer to emit the MCP notification so
clients can refresh: when the underlying enabled configs change (the source used
by getEnabledConfigs()), call the MCP notification
"notifications/tools/list_changed" with the new tools payload (same shape
produced by the ListToolsRequestSchema handler) via the server notifier used by
this class; hook this emission into the place where configs are mutated or
watched (or add a watcher that compares snapshots from getEnabledConfigs()) so
any change triggers a notification—ensure tool naming/annotation logic matches
the ListToolsRequestSchema handler and reuse the same transformation used in the
setRequestHandler for ListToolsRequestSchema and keep behavior for
CallToolRequestSchema/performBuiltinKnowledgeSearch unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/presenter/knowledgePresenter/index.ts`:
- Around line 163-184: The delete method currently awaits
this.storePresenterInitTasks.get(id) which can throw if an in-flight
createStorePresenter rejected, aborting disk cleanup; wrap the await in a
try/catch (or use .catch()) so initialization rejections are swallowed and
treated as undefined, then proceed to check this.getStorePresenter(id) and the
on-disk removal logic; still call rag.destroy() if a valid rag exists and ensure
this.storePresenterCache.delete(id) runs in the same finally branch, and leave
the fs.rmSync calls for storageDir/id and storageDir/id + '.wal' untouched so
broken DB files are removed even after a failed init.

In `@src/shared/contracts/domainSchemas.ts`:
- Line 344: The env schema now allows non-string values but the stdio spawn and
in-memory servers don't coerce them, causing type mismatches; update the code
that copies serverConfig.env (e.g., the loop in mcpClient.ts that assigns to env
before constructing StdioClientTransport) to coerce each value with String(value
?? '') and skip undefined, and similarly update any in-memory server usages
(e.g., BochaSearchServer where env.apiKey is cast) to validate/coerce values via
String(...) before use so all places pass Record<string,string> to child
processes and handlers.

In `@test/main/lib/agentRuntime/rtkRuntimeService.test.ts`:
- Around line 263-267: The test currently asserts three explicit runCommand
invocations and is tied to the redundant system-candidate probe produced by
verifyRuntimeCandidate; update the assertion in rtkRuntimeService.test.ts so it
no longer requires the duplicated ['rtk', ['--version']] entry: either expect
only the two unique probes (for the explicit candidate and system when distinct)
or make the expectation order/duplication-agnostic (e.g., assert that the calls
include the expected unique command/arg pairs or check the call count for system
when source === 'system'); locate the assertion that maps runCommand.mock.calls
and change it to validate uniqueness or use arrayContaining/Set-style checks so
the test will still pass if you refactor verifyRuntimeCandidate to skip the
redundant probe.

---

Outside diff comments:
In `@src/main/presenter/knowledgePresenter/index.ts`:
- Around line 140-157: The update method can throw when awaiting a
previously-rejected init promise from storePresenterInitTasks which poisons the
whole reconciliation; change the await of
this.storePresenterInitTasks.get(config.id) inside update to be guarded so a
rejected init does not rethrow (for example, retrieve the promise, await it with
a try/catch or use .catch(() => undefined) and proceed only if it resolved to an
object), keep the existing behavior of calling
initializingRag.updateConfig(config) when the init succeeded, and leave
closeStorePresenterIfExists/config-disabled path unchanged; refer to update,
storePresenterInitTasks, getStorePresenter and createStorePresenter to locate
the logic to modify.

---

Nitpick comments:
In `@src/main/lib/agentRuntime/rtkRuntimeService.ts`:
- Around line 600-611: The second redundant probe calling
this.runCommandImpl('rtk', ['--version'], ...) should be skipped when the
runtime candidate is the system one; detect candidate.source === 'system'
(and/or candidate.command === 'rtk') and short-circuit before calling
runCommandImpl a second time so you only perform the PATH-resolvability check
for bundled candidates (where prependBundledRuntimeToEnv was applied). Ensure
the existing RtkHealthCheckError logic remains for failures of the bundled check
and do not change error shape—only avoid invoking runCommandImpl for system
candidates.

In `@src/main/presenter/configPresenter/index.ts`:
- Around line 2641-2653: The current setKnowledgeConfigs calls getMcpServers()
(an expensive, side-effecting method) just to broadcast
MCP_EVENTS.CONFIG_CHANGED, which wrongly couples config saves to MCP
normalization and re-emits full MCP payloads; change setKnowledgeConfigs (which
calls this.knowledgeConfHelper.setKnowledgeConfigs) to avoid calling
getMcpServers — either emit a new dedicated event (e.g.,
MCP_EVENTS.KNOWLEDGE_CONFIG_CHANGED or a lightweight event) via eventBus.send
with no or minimal payload, or read the raw MCP store values directly (not via
getMcpServers) if listeners need raw data; ensure
KnowledgePresenter.syncKnowledgeConfigChanges remains unchanged (it already
re-reads configP.getKnowledgeConfigs()), and remove the
Promise.all([this.getMcpServers(), this.getMcpEnabled()]) usage to eliminate the
async dependency on knowledgePresenter.isSupported().

In `@src/main/presenter/configPresenter/mcpConfHelper.ts`:
- Around line 461-491: The code always creates mergedConfigs =
[...existingConfigs] and writes mcpServers to the store even when no legacy
configs are added; change the logic in the block handling env.configs so you
first build legacyConfigs, check whether any config.id in legacyConfigs is not
present in existingIds, and only if at least one new id exists create
mergedConfigs, mutate mcpServers.builtinKnowledge.env (delete configs) and call
this.mcpStore.set('mcpServers', mcpServers); otherwise skip the store write and
simply return existingConfigs (preserve the original array reference). Ensure
you reference the existing symbols env.configs, existingConfigs, legacyConfigs,
mergedConfigs, existingIds, migratedEnv, mcpServers.builtinKnowledge and
this.mcpStore.set when making the change.

In `@src/main/presenter/knowledgePresenter/index.ts`:
- Around line 80-85: The current listener on eventBus for
MCP_EVENTS.CONFIG_CHANGED calls syncKnowledgeConfigChanges() for every MCP
config change, causing unnecessary work; update the code to only react to
knowledge-config saves by either (a) subscribing to a new dedicated event
emitted by configPresenter.setKnowledgeConfigs (e.g.,
MCP_EVENTS.KNOWLEDGE_CONFIG_CHANGED) or (b) inspecting the CONFIG_CHANGED event
payload inside the eventBus.on callback and only invoking
this.syncKnowledgeConfigChanges() when the change pertains to knowledge configs;
locate the handler using eventBus.on(MCP_EVENTS.CONFIG_CHANGED, ...) and the
method syncKnowledgeConfigChanges to implement the conditional or switch to the
new event name.

In `@src/main/presenter/mcpPresenter/inMemoryServers/builtinKnowledgeServer.ts`:
- Around line 38-67: The server never notifies clients when the runtime tool
list changes; update builtinKnowledgeServer to emit the MCP notification so
clients can refresh: when the underlying enabled configs change (the source used
by getEnabledConfigs()), call the MCP notification
"notifications/tools/list_changed" with the new tools payload (same shape
produced by the ListToolsRequestSchema handler) via the server notifier used by
this class; hook this emission into the place where configs are mutated or
watched (or add a watcher that compares snapshots from getEnabledConfigs()) so
any change triggers a notification—ensure tool naming/annotation logic matches
the ListToolsRequestSchema handler and reuse the same transformation used in the
setRequestHandler for ListToolsRequestSchema and keep behavior for
CallToolRequestSchema/performBuiltinKnowledgeSearch unchanged.

In `@src/renderer/settings/components/BuiltinKnowledgeSettings.vue`:
- Around line 936-942: loadBuiltinConfig currently swallows errors with
console.error, so surface failures to the user and prevent accidental
overwrites: when catching errors from configClient.getKnowledgeConfigs() (in
loadBuiltinConfig), call the app's user-facing notification/toast API to show
the failure (include the error message) and set a boolean flag like
builtinConfigsLoaded/hasLoadedBuiltinConfigs to false; on successful load set it
to true and populate builtinConfigs.value; update the save path (e.g., the save
handler or saveBuiltinConfig and any save button disabled state) to guard
against saving when builtinConfigsLoaded is false so saves are disabled or
rejected until a successful load completes.

In `@src/shared/contracts/domainSchemas.ts`:
- Around line 21-33: BuiltinKnowledgeConfigSchema uses bare z.number() which
accepts NaN and negatives (Zod v4 z.number() does accept NaN by default), so
update the schema to validate numeric fields: change dimensions and
fragmentsNumber to z.number().int().positive(), change chunkSize and
chunkOverlap to z.number().finite().nonnegative().optional() (or
.int().nonnegative() if integers are expected), and keep any optional chaining
(.optional()) as needed so invalid NaN/negative values are rejected at the
contract boundary; apply these changes on the BuiltinKnowledgeConfigSchema
definition to prevent NaN/null propagation.

In `@test/main/lib/agentRuntime/rtkRuntimeService.test.ts`:
- Around line 69-118: The helper duplication can be removed by parameterizing
the existing createService factory so tests reuse it instead of a separate
createHealthCheckService: update createService to accept an optional overrides
object (e.g., replaceWithRuntimeCommand, getShellEnvironment, runCommand,
getPath) and move the unique replaceWithRuntimeCommand mock currently in
createHealthCheckService into that overrides parameter; then replace
createHealthCheckService usages with calls to createService({
replaceWithRuntimeCommand: vi.fn((command)=> command === 'rtk' ?
'/runtime/rtk/rtk.exe' : command), runCommand }) so tests keep behavior while
eliminating the duplicate function. Ensure symbols referenced are createService,
createHealthCheckService, and replaceWithRuntimeCommand when making the change.

In `@test/main/presenter/mcpPresenter/builtinKnowledgeServer.test.ts`:
- Around line 1-118: The tests are in the wrong location and use the banned
`Function` type: move the test file so its test tree mirrors the source tree
under the same presenter/inMemoryServers structure (so it lives alongside the
source it tests) and replace the use of bare Function for serverInstances and
the mocked setRequestHandler signature with a precise function type such as
(input?: unknown) => Promise<unknown> (or a synchronous variant if appropriate)
to satisfy `@typescript-eslint/ban-types` and make the handler contract explicit;
update references to serverInstances, mockGetKnowledgeConfigs,
mockSimilarityQuery and the setRequestHandler mock implementation accordingly.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6690c786-f1d5-404f-b05d-862b1e9661fe

📥 Commits

Reviewing files that changed from the base of the PR and between c4ce7be and 7362d09.

📒 Files selected for processing (24)
  • src/main/lib/agentRuntime/rtkRuntimeService.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/configPresenter/mcpConfHelper.ts
  • src/main/presenter/knowledgePresenter/index.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/builder.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/builtinKnowledgeServer.ts
  • src/main/routes/config/configRouteHandler.ts
  • src/main/routes/models/modelRouteHandler.ts
  • src/renderer/api/ConfigClient.ts
  • src/renderer/settings/components/BuiltinKnowledgeSettings.vue
  • src/shared/contracts/domainSchemas.ts
  • src/shared/contracts/routes.ts
  • src/shared/contracts/routes/config.routes.ts
  • test/main/lib/agentRuntime/rtkRuntimeService.test.ts
  • test/main/presenter/KnowledgePresenter.test.ts
  • test/main/presenter/configPresenter/mcpConfHelper.test.ts
  • test/main/presenter/configPresenter/providerModelHelper.test.ts
  • test/main/presenter/mcpPresenter/builtinKnowledgeServer.test.ts
  • test/main/routes/dispatcher.test.ts
  • test/main/routes/models/modelRouteHandler.test.ts
  • test/renderer/api/clients.test.ts
  • test/renderer/components/BuiltinKnowledgeSettings.test.ts
  • test/renderer/stores/modelStore.test.ts
  • test/setup.renderer.ts

args: z.array(z.string()).optional(),
name: z.string().optional(),
env: z.record(z.string(), z.string()).optional()
env: z.record(z.string(), z.unknown()).optional()

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all reads of MCPServerConfig.env or .env[key] to confirm each handles unknown values.
rg -nP --type=ts -C3 '\b(server|config|cfg|mcpServer)\.env\b'
rg -nP --type=ts -C3 'MCPServerConfig\b'

Repository: ThinkInAIXYZ/deepchat

Length of output: 42122


🏁 Script executed:

# Check the normalizeStringRecordToArray implementation
sed -n '1,27p' src/main/presenter/llmProviderPresenter/acp/mcpConfigConverter.ts

# Check agentRepository usage of env
sed -n '470,480p' src/main/presenter/agentRepository/index.ts

# Search for child process spawning with env
rg -n "spawn|exec|execFile" --type=ts src/main/presenter/mcpPresenter/ -A 5 -B 2

Repository: ThinkInAIXYZ/deepchat

Length of output: 10049


🏁 Script executed:

# Look for serverManager implementation that spawns MCP processes
rg -n "serverManager\|spawn.*env" --type=ts src/main/presenter/mcpPresenter/ -l | head -5

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Search more broadly for env variable usage in process spawning
rg -n "env\s*:" --type=ts src/main/presenter/ | grep -i "spawn\|exec\|process" | head -10

Repository: ThinkInAIXYZ/deepchat

Length of output: 1020


🏁 Script executed:

# Find serverManager implementation
find src/main/presenter/mcpPresenter -name "*manager*" -o -name "*server*" | grep -v node_modules | head -10

Repository: ThinkInAIXYZ/deepchat

Length of output: 114


🏁 Script executed:

# Look for actual child process spawning with MCP servers
rg -n "spawn\|ChildProcess" --type=ts src/main/presenter/mcpPresenter/ -B 2 -A 5

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Check agentRepository context - what happens after env is retrieved
sed -n '470,490p' src/main/presenter/agentRepository/index.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 540


🏁 Script executed:

# Search for stdio server startup code that uses config.env
rg -n "stdio\|StdioClientTransport" --type=ts src/main/presenter/ -B 3 -A 3 | head -50

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Examine serverManager.ts - the critical file
cat src/main/presenter/mcpPresenter/serverManager.ts | head -200

Repository: ThinkInAIXYZ/deepchat

Length of output: 6776


🏁 Script executed:

# Continue reading serverManager.ts
wc -l src/main/presenter/mcpPresenter/serverManager.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 118


🏁 Script executed:

# Get the startServer method and process spawning code
sed -n '200,308p' src/main/presenter/mcpPresenter/serverManager.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 3282


🏁 Script executed:

# Find McpClient and check its connect method
find src/main/presenter/mcpPresenter -name "*mcpClient*" -o -name "*client*" | grep -v node_modules

Repository: ThinkInAIXYZ/deepchat

Length of output: 110


🏁 Script executed:

# Read McpClient to find spawn calls
cat src/main/presenter/mcpPresenter/mcpClient.ts | head -150

Repository: ThinkInAIXYZ/deepchat

Length of output: 4910


🏁 Script executed:

# Find where StdioClientTransport is created and env is used
rg -n "StdioClientTransport\|spawn\|processCommand" src/main/presenter/mcpPresenter/mcpClient.ts -A 10 -B 2

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Search more broadly in mcpClient.ts
wc -l src/main/presenter/mcpPresenter/mcpClient.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 115


🏁 Script executed:

# Get the middle section of mcpClient.ts where connect is likely defined
sed -n '150,350p' src/main/presenter/mcpPresenter/mcpClient.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 6996


🏁 Script executed:

# Continue reading to see where serverConfig.env is used for stdio
sed -n '300,450p' src/main/presenter/mcpPresenter/mcpClient.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 5106


🏁 Script executed:

# Check the inmemory server env handling
sed -n '190,210p' src/main/presenter/mcpPresenter/mcpClient.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 1026


🏁 Script executed:

# Check what getInMemoryServer does with env
rg -n "getInMemoryServer" src/main/presenter/mcpPresenter/ -A 10

Repository: ThinkInAIXYZ/deepchat

Length of output: 2974


🏁 Script executed:

# Check how in-memory servers use env
rg -n "env" src/main/presenter/mcpPresenter/inMemoryServers/builder.ts -B 2 -A 5

Repository: ThinkInAIXYZ/deepchat

Length of output: 1700


🏁 Script executed:

# Check if BochaSearchServer or other servers coerce env values
head -100 src/main/presenter/mcpPresenter/inMemoryServers/bochaSearchServer.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 2805


🏁 Script executed:

# Check BochaSearchServer constructor more to see how it handles apiKey
sed -n '75,150p' src/main/presenter/mcpPresenter/inMemoryServers/bochaSearchServer.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 2139


🏁 Script executed:

# Check if there are runtime coercions when env values are actually used
rg -n "apiKey\|env\[" src/main/presenter/mcpPresenter/inMemoryServers/bochaSearchServer.ts | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Verify how StdioClientTransport from MCP SDK handles env types
# First check if there's type validation in the codebase
rg -n "StdioClientTransport" src/ --type=ts -B 3 -A 10 | head -50

Repository: ThinkInAIXYZ/deepchat

Length of output: 3228


Coerce env values to strings in stdio server spawning and in-memory servers.

The schema change to Record<string, unknown> creates a type safety gap. While the ACP converter correctly coerces via String(value ?? ''), the stdio server path in mcpClient.ts (lines 354–365) and in-memory servers do not:

  • Stdio servers (line 354–365): Directly assigns serverConfig.env values to the env dict without coercion, then passes to StdioClientTransport which requires Record<string, string> for child process spawning.
  • In-memory servers (e.g., BochaSearchServer line 102): Unsafe cast env.apiKey as string without validation or coercion.

When non-string values (e.g., numbers, booleans) are stored in the config, they will be passed to child processes or used directly in handlers without conversion to strings, causing silent type mismatches.

Example unsafe path (mcpClient.ts lines 354–365)
Object.entries(this.serverConfig.env as Record<string, string>).forEach(
  ([key, value]) => {
    if (value !== undefined) {
      env[key] = value  // No coercion—direct assignment of potentially non-string value
    }
  }
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/contracts/domainSchemas.ts` at line 344, The env schema now allows
non-string values but the stdio spawn and in-memory servers don't coerce them,
causing type mismatches; update the code that copies serverConfig.env (e.g., the
loop in mcpClient.ts that assigns to env before constructing
StdioClientTransport) to coerce each value with String(value ?? '') and skip
undefined, and similarly update any in-memory server usages (e.g.,
BochaSearchServer where env.apiKey is cast) to validate/coerce values via
String(...) before use so all places pass Record<string,string> to child
processes and handlers.

Comment thread test/main/lib/agentRuntime/rtkRuntimeService.test.ts Outdated

@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: 6

🧹 Nitpick comments (4)
src/main/presenter/mcpPresenter/mcpClient.ts (1)

356-374: Consider skipping empty PATH values to avoid a leading empty entry.

If a user-provided env.PATH is null (or anything that coerces to ''), the current logic still merges it: env[currentPathKey] = '' + ':' + env[currentPathKey]. On *nix, a leading empty entry in PATH is interpreted as the current working directory, which is generally undesirable (and historically a PATH-injection footgun). Non-PATH variables setting an empty string is fine.

♻️ Proposed guard
             if (value !== undefined) {
               const stringValue = String(value ?? '')
               // 如果是PATH相关变量,合并到主PATH中
               if (['PATH', 'Path', 'path'].includes(key)) {
+                if (!stringValue) {
+                  return
+                }
                 const currentPathKey = process.platform === 'win32' ? 'Path' : 'PATH'
                 const separator = process.platform === 'win32' ? ';' : ':'
                 env[currentPathKey] = env[currentPathKey]
                   ? `${stringValue}${separator}${env[currentPathKey]}`
                   : stringValue
               } else {
                 env[key] = stringValue
               }
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/mcpClient.ts` around lines 356 - 374, The
merging logic for PATH-like keys in mcpClient.ts can introduce a leading empty
PATH entry when serverConfig.env contains null/empty values; update the block
that handles ['PATH','Path','path'] inside the Object.entries loop (where it
computes currentPathKey, separator and sets env[currentPathKey]) to first skip
merging when stringValue is empty (e.g., stringValue === ''), so that empty PATH
values are ignored, while keeping the existing behavior for non-PATH keys
(env[key] = stringValue) and preserving platform-specific
currentPathKey/separator logic.
src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts (1)

73-98: Disabled configs still fail hard validation, blocking all others.

The validation in Lines 81–89 throws unconditionally on the first config with any missing required field, even if that config is enabled: false. With the migration from legacy storage shapes introduced by this PR, a stale/disabled entry with an empty apiKey will now prevent the entire Dify server from starting and silently break every other (enabled) knowledge base in the same server.

Consider either skipping disabled entries, or collecting errors and continuing for valid+enabled configs.

♻️ Sketch — skip invalid/disabled instead of throwing
     for (const env of envs) {
       const config = env && typeof env === 'object' ? (env as Record<string, unknown>) : {}
       const apiKey = String(config.apiKey ?? '')
       const datasetId = String(config.datasetId ?? '')
       const description = String(config.description ?? '')
       const endpoint = String(config.endpoint ?? '') || 'https://api.dify.ai/v1'
+      const enabled =
+        config.enabled === true || String(config.enabled ?? '').toLowerCase() === 'true'

-      if (!apiKey) {
-        throw new Error('需要提供Dify API Key')
-      }
-      if (!datasetId) {
-        throw new Error('需要提供Dify Dataset ID')
-      }
-      if (!description) {
-        throw new Error('需要提供对这个知识库的描述,以方便ai决定是否检索此知识库')
-      }
+      if (!apiKey || !datasetId || !description) {
+        if (enabled) {
+          console.warn('DifyKnowledgeServer: skipping enabled config with missing fields', {
+            hasApiKey: !!apiKey,
+            hasDatasetId: !!datasetId,
+            hasDescription: !!description
+          })
+        }
+        continue
+      }

-      this.configs.push({
-        apiKey,
-        datasetId,
-        endpoint,
-        description,
-        enabled: config.enabled === true || String(config.enabled ?? '').toLowerCase() === 'true'
-      })
+      this.configs.push({ apiKey, datasetId, endpoint, description, enabled })
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts`
around lines 73 - 98, The loop in difyKnowledgeServer.ts currently throws on
missing apiKey/datasetId/description for every env even when config.enabled is
false; change the validation flow in the for (const env of envs) loop to
determine the enabled flag first (use the same enabled expression used in
this.configs.push) and if enabled is false, skip strict validation and either
continue (preferable) or collect and log the error instead of throwing; only
perform and throw on missing required fields for entries where enabled === true,
and still push or register disabled entries as needed via this.configs.push with
the computed enabled value.
src/main/presenter/mcpPresenter/inMemoryServers/ragflowKnowledgeServer.ts (1)

70-96: Same disabled-config validation issue + cross-file duplication.

Two points on this loop:

  1. Same as in difyKnowledgeServer.ts and fastGptKnowledgeServer.ts: a disabled config with empty apiKey / datasetIds / description will throw at Lines 79–87 and prevent any other (valid, enabled) RAGFlow knowledge base from initializing. Consider skipping invalid entries (warn-and-continue), at least for enabled === false.

  2. The coercion + normalization block is now near-identical across all three knowledge servers (dify, fastgpt, ragflow). Worth factoring out a small helper, e.g. in a shared module under inMemoryServers/:

♻️ Sketch — shared helpers
// e.g. src/main/presenter/mcpPresenter/inMemoryServers/knowledgeConfigUtils.ts
export const toStr = (v: unknown): string => String(v ?? '')

export const toBool = (v: unknown): boolean =>
  v === true || toStr(v).toLowerCase() === 'true'

export const toRecord = (v: unknown): Record<string, unknown> =>
  v && typeof v === 'object' ? (v as Record<string, unknown>) : {}

export const toStringArray = (v: unknown): string[] =>
  Array.isArray(v) ? v.map((x) => toStr(x)).filter(Boolean) : []

Each server then just consumes these, keeping the per-vendor validation/defaulting logic local.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/ragflowKnowledgeServer.ts`
around lines 70 - 96, The loop in ragflowKnowledgeServer.ts currently throws for
empty apiKey/datasetIds/description which will abort initialization if any
disabled entry is malformed; update the logic to coerce/normalize via shared
helpers (e.g. extract to
src/main/presenter/mcpPresenter/inMemoryServers/knowledgeConfigUtils.ts
providing toStr, toBool, toRecord, toStringArray) and in the loop use
toRecord(env) -> config, apiKey = toStr(...), datasetIds = toStringArray(...),
description = toStr(...), enabled = toBool(config.enabled); then skip invalid
entries by continuing (and optionally log/warn) when enabled === false and
required fields are missing, while still throwing or failing only for enabled
entries with missing required fields; also refactor similar blocks in
difyKnowledgeServer.ts and fastGptKnowledgeServer.ts to consume the same
helpers.
src/main/presenter/knowledgePresenter/index.ts (1)

353-356: closeAll may leave maps populated if any rag.close() rejects.

Promise.all rejects on the first failing close, so Lines 354–355 don't run and the cache/init-task maps retain stale references. On a subsequent call (or app shutdown timing), those references can be re-used. Switch to Promise.allSettled and clear maps in finally so cleanup is best-effort.

♻️ Proposed refactor
-    await Promise.all(Array.from(stores).map((rag) => rag.close()))
-    this.storePresenterCache.clear()
-    this.storePresenterInitTasks.clear()
+    try {
+      await Promise.allSettled(Array.from(stores).map((rag) => rag.close()))
+    } finally {
+      this.storePresenterCache.clear()
+      this.storePresenterInitTasks.clear()
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/knowledgePresenter/index.ts` around lines 353 - 356, The
closeAll method currently uses Promise.all on Array.from(stores).map(rag =>
rag.close()), which will abort on the first rejection and prevent the subsequent
clearing of this.storePresenterCache and this.storePresenterInitTasks; change
the logic to use Promise.allSettled for awaiting all rag.close() operations and
move the two map clear calls into a finally block so they always run (reference
the closeAll function, each rag.close() invocation, and the
this.storePresenterCache and this.storePresenterInitTasks maps).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/presenter/knowledgePresenter/index.ts`:
- Around line 207-220: The try/catch around creating KnowledgeStorePresenter
leaks the opened DuckDBPresenter (db) if construction throws and also discards
the original error; update the initTask closure to catch exceptions from new
KnowledgeStorePresenter(db, config, this.taskP), call the appropriate DB cleanup
(e.g., db.close() or db.dispose()) before rethrowing, and rethrow using the
Error constructor with the cause option (new Error('Failed to create
storePresenter', { cause: e })) or rethrow the original error to preserve stack;
reference getVectorDatabasePresenter, KnowledgeStorePresenter, db, initTask and
storePresenterCache when making the change so you clean up the db on failure and
only cache rag after successful construction.
- Around line 148-152: The update() method can abort if a prior concurrent init
rejected because awaiting this.storePresenterInitTasks.get(config.id) will
throw; change the await to swallow a rejected init the same way delete() does
(i.e., await this.storePresenterInitTasks.get(config.id).catch(() => undefined))
so initializingRag is undefined on failure and update() proceeds to apply
enable/disable or new config; update the reference in the update() function
where initializingRag is assigned and mirror the same pattern used in delete().
- Around line 269-279: closeStorePresenterIfExists currently awaits
this.storePresenterInitTasks.get(id) directly so a rejection from the in-flight
createStorePresenter can bubble up and skip cache cleanup; change it to mirror
the defensive pattern used in delete(): retrieve the init promise without
letting its rejection escape (e.g., const initPromise =
this.storePresenterInitTasks.get(id); let initializingRag; try { initializingRag
= await initPromise } catch { initializingRag = undefined } then use const rag =
this.getStorePresenter(id) ?? initializingRag, call await rag.close() if rag
exists, and ensure this.storePresenterCache.delete(id) runs in a finally block
so the cache is always cleaned up even on init failures.
- Around line 163-184: The delete method can leave a stale promise in
storePresenterInitTasks causing races; after resolving initializingRag (from
this.storePresenterInitTasks.get(id)) and before/after destroying the rag,
remove the init-task entry from this.storePresenterInitTasks (e.g., call
this.storePresenterInitTasks.delete(id)) so concurrent getOrCreateStorePresenter
/ createStorePresenter cannot pick up a resolved-but-destroyed presenter; also
ensure you still delete the storePresenterCache entry as currently done and
perform the init-task deletion inside the same try/finally so it always runs.

In `@src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts`:
- Line 62: The constructor for DifyKnowledgeServer currently logs the entire env
object (console.log('DifyKnowledgeServer constructor', env)), which exposes
sensitive apiKey values; update the DifyKnowledgeServer constructor to stop
printing the raw env - either remove the console.log entirely or log a redacted
summary (e.g., only keys or mask apiKey values) and apply the same change to the
analogous log in fastGptKnowledgeServer (the constructor/log at Line 51) so no
full config or apiKey is emitted to logs.

In `@src/main/presenter/mcpPresenter/inMemoryServers/fastGptKnowledgeServer.ts`:
- Line 51: Remove the unsafe console.log in the FastGptKnowledgeServer
constructor that prints the entire env (including env.configs with apiKey
values); either drop the log entirely or replace it with a safe debug message
that omits or masks sensitive fields (e.g., iterate env.configs and replace
apiKey values with a fixed redaction token) and/or log only non-sensitive keys.
Ensure the change targets the FastGptKnowledgeServer constructor and the
env.configs structure so no API keys are written to logs.

---

Nitpick comments:
In `@src/main/presenter/knowledgePresenter/index.ts`:
- Around line 353-356: The closeAll method currently uses Promise.all on
Array.from(stores).map(rag => rag.close()), which will abort on the first
rejection and prevent the subsequent clearing of this.storePresenterCache and
this.storePresenterInitTasks; change the logic to use Promise.allSettled for
awaiting all rag.close() operations and move the two map clear calls into a
finally block so they always run (reference the closeAll function, each
rag.close() invocation, and the this.storePresenterCache and
this.storePresenterInitTasks maps).

In `@src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts`:
- Around line 73-98: The loop in difyKnowledgeServer.ts currently throws on
missing apiKey/datasetId/description for every env even when config.enabled is
false; change the validation flow in the for (const env of envs) loop to
determine the enabled flag first (use the same enabled expression used in
this.configs.push) and if enabled is false, skip strict validation and either
continue (preferable) or collect and log the error instead of throwing; only
perform and throw on missing required fields for entries where enabled === true,
and still push or register disabled entries as needed via this.configs.push with
the computed enabled value.

In `@src/main/presenter/mcpPresenter/inMemoryServers/ragflowKnowledgeServer.ts`:
- Around line 70-96: The loop in ragflowKnowledgeServer.ts currently throws for
empty apiKey/datasetIds/description which will abort initialization if any
disabled entry is malformed; update the logic to coerce/normalize via shared
helpers (e.g. extract to
src/main/presenter/mcpPresenter/inMemoryServers/knowledgeConfigUtils.ts
providing toStr, toBool, toRecord, toStringArray) and in the loop use
toRecord(env) -> config, apiKey = toStr(...), datasetIds = toStringArray(...),
description = toStr(...), enabled = toBool(config.enabled); then skip invalid
entries by continuing (and optionally log/warn) when enabled === false and
required fields are missing, while still throwing or failing only for enabled
entries with missing required fields; also refactor similar blocks in
difyKnowledgeServer.ts and fastGptKnowledgeServer.ts to consume the same
helpers.

In `@src/main/presenter/mcpPresenter/mcpClient.ts`:
- Around line 356-374: The merging logic for PATH-like keys in mcpClient.ts can
introduce a leading empty PATH entry when serverConfig.env contains null/empty
values; update the block that handles ['PATH','Path','path'] inside the
Object.entries loop (where it computes currentPathKey, separator and sets
env[currentPathKey]) to first skip merging when stringValue is empty (e.g.,
stringValue === ''), so that empty PATH values are ignored, while keeping the
existing behavior for non-PATH keys (env[key] = stringValue) and preserving
platform-specific currentPathKey/separator logic.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6d78016-e79b-4b05-8056-7c449218a1e5

📥 Commits

Reviewing files that changed from the base of the PR and between 7362d09 and 4b93f13.

📒 Files selected for processing (12)
  • src/main/presenter/knowledgePresenter/index.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/bochaSearchServer.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/braveSearchServer.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/builder.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/deepResearchServer.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/fastGptKnowledgeServer.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/ragflowKnowledgeServer.ts
  • src/main/presenter/mcpPresenter/mcpClient.ts
  • test/main/lib/agentRuntime/rtkRuntimeService.test.ts
  • test/main/presenter/KnowledgePresenter.test.ts
  • test/main/presenter/mcpClient.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/presenter/mcpPresenter/inMemoryServers/braveSearchServer.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/presenter/mcpPresenter/inMemoryServers/builder.ts
  • test/main/presenter/KnowledgePresenter.test.ts
  • test/main/lib/agentRuntime/rtkRuntimeService.test.ts

Comment thread src/main/presenter/knowledgePresenter/index.ts
Comment thread src/main/presenter/knowledgePresenter/index.ts
Comment thread src/main/presenter/knowledgePresenter/index.ts
Comment thread src/main/presenter/knowledgePresenter/index.ts
Comment thread src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts Outdated
Comment thread src/main/presenter/mcpPresenter/inMemoryServers/fastGptKnowledgeServer.ts Outdated

@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.

🧹 Nitpick comments (2)
src/main/presenter/mcpPresenter/inMemoryServers/fastGptKnowledgeServer.ts (1)

50-86: Consider extracting the shared config-normalization logic.

The constructor bodies of DifyKnowledgeServer, FastGptKnowledgeServer, and (per the PR summary) RagflowKnowledgeServer now share an identical pattern: validate env, iterate env.configs, coerce apiKey/datasetId/description to strings, default endpoint, and normalize enabled. A small helper (e.g. normalizeKnowledgeConfigs(env, { defaultEndpoint, errorMessages })) would remove the triplicated logic and make future fixes (e.g. handling additional truthy enabled values) a one-liner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/fastGptKnowledgeServer.ts`
around lines 50 - 86, Extract the duplicated config-validation and normalization
from the constructors of DifyKnowledgeServer, FastGptKnowledgeServer, and
RagflowKnowledgeServer into a single helper (e.g. normalizeKnowledgeConfigs)
that accepts the raw env plus options like defaultEndpoint and errorMessages;
the helper should validate env and env.configs, iterate configs coercing apiKey,
datasetId, description to strings, apply the default endpoint, and normalize
enabled (treat true, 'true', '1', 1, and other desired truthy forms as enabled),
then return the normalized array; update each class constructor to call
normalizeKnowledgeConfigs(...) and assign the returned array to this.configs,
and remove the in-constructor duplication.
src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts (1)

61-97: Good hardening; consider renaming the inner loop variable.

Removing the console.log(env) and coercing untrusted fields to strings addresses the prior secret-leak concern and is consistent with the new untyped-env contract from builder.ts. One small readability nit: the loop variable env on Line 73 shadows the outer env parameter, making the inner block slightly harder to reason about.

♻️ Optional readability tweak
-    // 处理每个配置
-    for (const env of envs) {
-      const config = env && typeof env === 'object' ? (env as Record<string, unknown>) : {}
+    // 处理每个配置
+    for (const rawConfig of envs) {
+      const config =
+        rawConfig && typeof rawConfig === 'object' ? (rawConfig as Record<string, unknown>) : {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts`
around lines 61 - 97, The constructor currently shadows the outer parameter env
by reusing the name env in the for loop; rename the loop variable (e.g., to cfg,
envItem, or configRaw) to avoid shadowing and improve readability, then update
all references inside the loop (the config assignment,
apiKey/datasetId/description/endpoint extraction, and the this.configs.push
call) to use the new loop variable name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts`:
- Around line 61-97: The constructor currently shadows the outer parameter env
by reusing the name env in the for loop; rename the loop variable (e.g., to cfg,
envItem, or configRaw) to avoid shadowing and improve readability, then update
all references inside the loop (the config assignment,
apiKey/datasetId/description/endpoint extraction, and the this.configs.push
call) to use the new loop variable name.

In `@src/main/presenter/mcpPresenter/inMemoryServers/fastGptKnowledgeServer.ts`:
- Around line 50-86: Extract the duplicated config-validation and normalization
from the constructors of DifyKnowledgeServer, FastGptKnowledgeServer, and
RagflowKnowledgeServer into a single helper (e.g. normalizeKnowledgeConfigs)
that accepts the raw env plus options like defaultEndpoint and errorMessages;
the helper should validate env and env.configs, iterate configs coercing apiKey,
datasetId, description to strings, apply the default endpoint, and normalize
enabled (treat true, 'true', '1', 1, and other desired truthy forms as enabled),
then return the normalized array; update each class constructor to call
normalizeKnowledgeConfigs(...) and assign the returned array to this.configs,
and remove the in-constructor duplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a35304f5-75c6-4109-b91f-3abacca67cd0

📥 Commits

Reviewing files that changed from the base of the PR and between 4b93f13 and 786fd65.

📒 Files selected for processing (4)
  • src/main/presenter/knowledgePresenter/index.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/fastGptKnowledgeServer.ts
  • test/main/presenter/KnowledgePresenter.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/main/presenter/KnowledgePresenter.test.ts

@zerob13
zerob13 merged commit c6ac348 into dev Apr 27, 2026
3 checks passed
zerob13 added a commit that referenced this pull request Apr 27, 2026
* fix(rtk): simplify health check

* fix(knowledge): use config ipc

* fix(models): persist db model status

* fix: harden MCP env and knowledge delete

* fix: clean knowledge presenter cleanup paths
@zhangmo8
zhangmo8 deleted the fix/rtk-status-check branch April 30, 2026 05:55
zerob13 added a commit that referenced this pull request May 1, 2026
* docs: add mac computer use spec

* docs: sync provider tables

* fix: rtk status (#1541)

* fix(rtk): simplify health check

* fix(knowledge): use config ipc

* fix(models): persist db model status

* fix: harden MCP env and knowledge delete

* fix: clean knowledge presenter cleanup paths

* refactor: enhance transition effects and performance across message components (#1542)

- Updated transition classes in MessageActionButtons.vue for smoother animations.
- Refactored MessageBlockContent.vue to optimize artifact snapshot handling with computed properties.
- Improved transition effects in MessageBlockToolCall.vue for better user experience.
- Added a mention icon map in MessageContent.vue to streamline icon retrieval.
- Enhanced MessageItemUser.vue with a new line counting function for better text handling.
- Optimized MessageToolbar.vue for consistent transition effects on button interactions.
- Refactored BrowserPanel.vue to simplify state management for synced bounds.
- Improved ChatSidePanel.vue with better resizing and visibility handling.
- Updated ChatPage.vue to enhance chat search highlight scheduling.
- Cleaned up ChatTabView.vue by removing legacy collapsed new chat button functionality.
- Enhanced tests in ChatTabView.test.ts and WindowSideBar.test.ts for improved coverage and accuracy.

* chore: update markstream-vue to 0.0.13 (#1544)

* fix: preserve interleaved reasoning (#1543)

* chore(release): prepare v1.0.4-beta.2

* fix(ipc): allow attachment date metadata (#1547)

* chore(release): prepare v1.0.4-beta.3

* feat: add mac computer use helper

* feat: enhance computer use guidance

* chore: bump acp registry versions

* fix: import mac signing identity for helper

* build(cua): vendor CUA driver source

* fix(cua): prefer element index click mode

* fix(cua): route zoom clicks by coordinates

* docs(cua): prefer visual fallback for sparse UI

* feat: update vendored cua driver

* fix(computer-use): improve error handling and testability

* docs(cua): add runtime plugin spec

* feat(plugin): add CUA runtime plugin

* feat: migrate computer use to plugin

* fix: surface plugin tools and permissions

* fix(plugin): use MCP-only CUA flow

* ci(plugin): release CUA dcplugin assets

* fix(plugin): hide CUA on unsupported OS

* feat(plugin): bundle official CUA plugin

* fix: harden plugin startup and CUA telemetry

* chore: update CUA driver vendor

* fix(build): sign CUA plugin helper

* fix: improve CUA window scoped vision fallback

* fix(cua): align pixel clicks with upstream

---------

Co-authored-by: yyhhyyyyyy <yyhhyyyyyy8@gmail.com>
Co-authored-by: xiaomo <wegi866@gmail.com>
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