Skip to content

fix(knowledge-tools): scope kb_* tools to composer-selected bases too#16743

Merged
0xfullex merged 6 commits into
mainfrom
slash-kb-not-triggering-tool
Jul 6, 2026
Merged

fix(knowledge-tools): scope kb_* tools to composer-selected bases too#16743
0xfullex merged 6 commits into
mainfrom
slash-kb-not-triggering-tool

Conversation

@eeee0717

@eeee0717 eeee0717 commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

🚨 Branch strategy — read before opening this PR

The v2 refactor has merged into main, so main is the default branch for active development (v1 and v2 code currently coexist there — expect large, breaking changes).

  • Active development (features, refactors, optimizations, fixes for the current codebase) → target main (the default base).
  • v1 maintenance (hotfixes and subsequent v1 releases) → branch from and target v1, not main.

A v1 fix does not auto-carry to main: if the same bug exists on main, open a separate forward-port PR targeting main. Before touching subsystems being replaced, read docs/references/data/ and watch for @deprecated markers — they flag code being deleted.

What this PR does

Before this PR: when the active assistant had no knowledge base statically bound, selecting one via the composer's / picker had no effect — the ai.stream_open request had no field to carry that per-turn selection, and the kb_search / kb_list / kb_read / kb_manage tools' exposure gate only checked the assistant's static knowledgeBaseIds. The model never even saw the tools as available, so it could not answer questions about the selected knowledge base.

After this PR: the composer's per-turn selection is threaded through AiStreamOpenRequestPersistentChatContextProviderbuildAgentParams, unioned with the assistant's statically bound bases, and used to gate/scope the four kb_* tools. Selecting a knowledge base via / now makes it searchable for that turn regardless of the assistant's static binding.

Fixes #

Why we need it and why it was done in this way

The following tradeoffs were made: the per-turn selection is unioned with the assistant's bound bases rather than overriding them (unlike the existing mcpToolIds "caller wins" pattern). The composer UI already restricts per-turn picks to a subset of the assistant's bound bases whenever any are configured, so union and override are equivalent in every reachable state today — union is simply safer against a future caller that passes an empty array.

The following alternatives were considered: mirroring the mcpToolIds caller-wins override; rejected because it could unintentionally shrink the assistant's default scope.

Links to places where the discussion took place: N/A

Breaking changes

None.

Special notes for your reviewer

RequestContext.knowledgeBaseIds / ToolApplyScope.knowledgeBaseIds are optional (default []) rather than required, so unrelated tests that construct minimal scope objects (registry/meta-tool tests) didn't need touching.

Checklist

This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR.
Approvers are expected to review this list.

  • Branch: This PR targets the correct branch — main for active development, v1 for v1 maintenance fixes
  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior.
  • Self-review: I have reviewed my own code (e.g., via /gh-pr-review, gh pr diff, or GitHub UI) before requesting review from others

Release note

Fix: selecting a knowledge base via the "/" picker in the chat composer now works even when the assistant has no knowledge base configured — previously the selection was silently ignored and the assistant never searched it.

The composer's per-turn "/" knowledge base picker was silently dropped
before it reached main: `ai.stream_open` had no field for it, and the
kb_search/kb_list/kb_read/kb_manage tools' exposure gate only checked
the assistant's static `knowledgeBaseIds`. When an assistant had no
bound knowledge base, selecting one via the composer had no effect —
the model never even saw the kb_* tools as available.

Thread the composer selection through AiStreamOpenRequest ->
PersistentChatContextProvider -> buildAgentParams, union it with the
assistant's bound bases, and gate/execute the four kb_* tools against
that effective scope instead of the assistant's bound ids alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
@eeee0717 eeee0717 requested a review from a team July 4, 2026 13:40
@eeee0717 eeee0717 requested review from 0xfullex and DeJeune as code owners July 4, 2026 13:40
@eeee0717 eeee0717 requested review from AtomsH4 and zhangjiadi225 July 4, 2026 13:41

@AtomsH4 AtomsH4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was translated automatically.

This change passes the composer's per-turn knowledge base selection through to ai.stream_open, PersistentChatContextProvider, buildAgentParams, and four kb_* tools, and updates the related tool unit tests. The direction is correct, but there are two issues that must be fixed first: effective knowledgeBaseIds is not being included in tool selection, causing KB tools to not actually be exposed; additionally, the main side directly trusting requestIds passed from the renderer will bypass the assistant's static KB binding boundary.


Original Content

这次改动把 composer 的 per-turn knowledge base 选择一路传到 ai.stream_open、PersistentChatContextProvider、buildAgentParams 和四个 kb_* tools,并更新了相关 tool 单测。方向是对的,但目前有两个必须先修的问题:effective knowledgeBaseIds 没有进入 tool selection,导致 KB tools 实际不会暴露;同时 main 侧直接信任 renderer 传入的 requestIds 会绕过 assistant 的静态 KB 绑定边界。

applyHttpTrace(sdkConfig, request.chatId, model)
const fileAttachments = collectFileAttachments(request.messages)
const hasFileAttachments = fileAttachments.length > 0
const knowledgeBaseIds = resolveKnowledgeBaseIds(assistant, request.knowledgeBaseIds)

@AtomsH4 AtomsH4 Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review comment was translated automatically.

[Blocker][A1] The knowledgeBaseIds calculated here is not passed into resolveTools() / registry.selectActive(), but the applies for the four kb_* tools have already been changed to check scope.knowledgeBaseIds. The actual path is: composer passes in request.knowledgeBaseIds, here we get a non-empty scope, but line 80 still calls resolveTools(request, assistant, model, hasFileAttachments); resolveTools() line 190's selectActive({ assistant, mcpToolIds, hasFileAttachments, hasAnyKnowledgeBase }) doesn't include knowledgeBaseIds, so KnowledgeSearchTool etc.'s applies all return false. The result is that not only the composer-selected KB that this PR is supposed to fix won't expose tools, but the originally assistant statically-bound KB tools will also be hidden. Minimal fix: pass the effective knowledgeBaseIds into resolveTools, and pass it to registry.selectActive({ ..., knowledgeBaseIds }); also add a buildAgentParams level test asserting that both assistant-bound and request-selected scenarios will select kb_* tools.


Original Content

[Blocker][A1] 这里算出的 knowledgeBaseIds 没有进入 resolveTools() / registry.selectActive(),但四个 kb_* tool 的 applies 已经改成检查 scope.knowledgeBaseIds。实际路径是:composer 传入 request.knowledgeBaseIds,这里得到非空 scope,但第 80 行仍调用 resolveTools(request, assistant, model, hasFileAttachments)resolveTools() 第 190 行的 selectActive({ assistant, mcpToolIds, hasFileAttachments, hasAnyKnowledgeBase }) 没带 knowledgeBaseIds,所以 KnowledgeSearchTool 等的 applies 都返回 false。结果不仅本 PR 要修的 composer-selected KB 不会暴露工具,原本 assistant 静态绑定的 KB 工具也会被隐藏。最小修复:把 effective knowledgeBaseIds 传进 resolveTools,并传给 registry.selectActive({ ..., knowledgeBaseIds });同时加一个 buildAgentParams 级别测试,断言 assistant-bound 和 request-selected 两种场景都会选中 kb_* 工具。

* not a replacement for it.
*/
export function resolveKnowledgeBaseIds(assistant: Assistant | undefined, requestIds: string[] | undefined): string[] {
return Array.from(new Set([...(assistant?.knowledgeBaseIds ?? []), ...(requestIds ?? [])]))

@AtomsH4 AtomsH4 Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review comment was translated automatically.

[Blocker][A4] Directly union-ing the requestIds from the renderer into the assistant's KB scope turns the assistant's static binding boundary into one that relies solely on UI constraints. Failure path: an assistant for a certain topic is only bound to kb-public, but the renderer/IPC request can send knowledgeBaseIds: ["kb-private"]; this function would return kb-public + kb-private, and after fixing the tool selection above, the model could access unbound private knowledge bases via kb_search/kb_read (kb_manage would also fall into the approvable write tool scope). The PR description mentions that the composer UI limits the selection range when the assistant has configured KBs, but the main-side IPC contract cannot trust this UI filtering. Minimum fix: when assistant.knowledgeBaseIds is non-empty, filter requestIds to that collection (or directly return the assistant scope, keeping the union from shrinking the default range); only allow request ids to expand the scope in scenarios where the assistant has no KB binding / no assistant selected-only case, and add test coverage for out-of-scope request ids being dropped.


Original Content

[Blocker][A4] 这里直接把 renderer 传来的 requestIds union 进 assistant 的 KB scope,会把 assistant 静态绑定边界变成仅靠 UI 约束。失败路径:某个 topic 的 assistant 只绑定了 kb-public,但 renderer/IPC 请求可以发送 knowledgeBaseIds: ["kb-private"];这个函数会返回 kb-public + kb-private,修复上面的 tool selection 后模型就能通过 kb_search/kb_read 访问未绑定的私有知识库(kb_manage 还会进入可审批的写工具范围)。PR 说明里提到 composer UI 会在 assistant 已配置 KB 时限制选择范围,但 main 侧 IPC 契约不能信任这个 UI 过滤。最小修复:当 assistant.knowledgeBaseIds 非空时,把 requestIds 过滤到该集合(或直接返回 assistant scope,保持 union 不缩小默认范围);只有 assistant 未绑定 KB / 无 assistant 的 selected-only 场景才允许 request ids 扩大 scope,并加测试覆盖 out-of-scope request id 被丢弃。

resolveTools() computed the effective knowledgeBaseIds but never passed it
into registry.selectActive(), so all kb_* tools' applies predicates always
saw an empty scope — a regression breaking even assistant-bound knowledge
bases. Separately, resolveKnowledgeBaseIds() unioned the assistant's static
binding with the renderer-supplied request ids, letting an IPC request
expand a tool's reach beyond what the assistant was configured for.

Thread knowledgeBaseIds through resolveTools() into selectActive(), and make
the assistant's static binding authoritative (not unioned) whenever it is
non-empty, falling back to the request-supplied ids only when the assistant
has no binding. Update the six sibling doc comments this PR introduced that
still described the old union semantics.

Addresses review feedback on PR #16743.

Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
@eeee0717

eeee0717 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review — both Blockers were real, confirmed against current HEAD, and now fixed in ede850d.

Fixed

  • A1 (resolveTools never actually received the computed knowledgeBaseIds, so registry.selectActive() always saw an empty scope and every kb_* tool's applies evaluated false — a regression that broke even the pre-existing assistant-bound case) → resolveTools() now takes knowledgeBaseIds as a parameter and forwards it into registry.selectActive({...}). See src/main/ai/runtime/aiSdk/params/buildAgentParams.ts:80 and :191-197.
  • A4 (resolveKnowledgeBaseIds unioned the assistant's static binding with the renderer/IPC-supplied request ids, letting a request smuggle in an out-of-scope base id and gain search/read/manage access beyond what the assistant was configured for) → the assistant's static binding is now authoritative whenever it's non-empty; the request-selected ids only define the scope when the assistant has no binding at all. See resolveKnowledgeBaseIds in the same file (buildAgentParams.ts:~150).

Both fixes are pinned by new tests in buildAgentParams.test.ts (resolveTools knowledge-base wiring and the updated resolveKnowledgeBaseIds describe block) — I verified each fails when the corresponding fix is reverted. While in there I also updated six sibling doc comments (in context.ts, types.ts, and the four Knowledge*Tool.ts files) that this same PR had introduced describing the old union semantics, so they no longer contradict the new trust-boundary behavior.

pnpm lint, full typecheck, and the full test suite (except a pre-existing, unrelated better-sqlite3 native-module ABI mismatch in this sandbox) are all green.

@eeee0717 eeee0717 requested a review from AtomsH4 July 6, 2026 03:10

@DeJeune DeJeune left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@zhangjiadi225 zhangjiadi225 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was translated automatically.

The current implementation direction has corrected the previous two blockers; there is only one contract comment remaining that is inconsistent with the new trust-boundary semantics, and it is recommended to update it accordingly.


Original Content

当前实现方向已经修正了前面两个 blocker;这里只剩一处契约注释与新 trust-boundary 语义不一致,建议同步更新。

Comment thread src/shared/ai/transport/stream.ts Outdated
topicId: string
/** UniqueModelIds selected by the composer model selector — Main dispatches one execution per model. */
mentionedModelIds?: UniqueModelId[]
/** Knowledge bases selected via the composer `/` picker for this turn — unioned with the assistant's own bound bases. */

@zhangjiadi225 zhangjiadi225 Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review comment was translated automatically.

[C6] The contract comment here still describes the old union semantics, but the current resolveKnowledgeBaseIds() has been changed so that when an assistant has static binding, the assistant scope takes precedence, and only when there is no static binding does it use the composer's current selection. I suggest changing "unioned with the assistant bound bases" here to reflect the current rules; similar outdated descriptions also exist in the AiBaseRequest.knowledgeBaseIds comment in src/main/ai/types/requests.ts, and in the file header of src/main/ai/tools/knowledgeLookup.ts where it says "AI-SDK path uses assistant knowledgeBaseIds". Otherwise, future callers may mistakenly think that KB ids passed from the renderer can extend the assistant scope.


Original Content

[C6] 这里的契约注释还在描述旧的 union 语义,但当前 resolveKnowledgeBaseIds() 已经改成 assistant 有静态绑定时以 assistant scope 为准,只有无静态绑定时才使用 composer 本轮选择。建议把这里的 “unioned with the assistant bound bases” 改成当前规则;同类旧描述也还在 src/main/ai/types/requests.tsAiBaseRequest.knowledgeBaseIds 注释,以及 src/main/ai/tools/knowledgeLookup.ts 文件头里 “AI-SDK path uses assistant knowledgeBaseIds” 的说明。否则后续调用方会误以为 renderer 传入的 KB id 可以扩展 assistant scope。

…horitative-scope rule

Three more contract comments (AiStreamOpenRequest.knowledgeBaseIds,
AiBaseRequest.knowledgeBaseIds, and the knowledgeLookup.ts file header)
still described the old union semantics that the prior fix removed —
misleading a future reader into thinking a renderer-supplied id can
expand an assistant's bound scope. Reworded all three to match
resolveKnowledgeBaseIds's actual either/or rule, using consistent
wording across all three sites.

Addresses review feedback on PR #16743.

Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
@eeee0717

eeee0717 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks — fixed in adbc8b6.

Fixed

  • [C6] The three contract comments still describing the old union semantics (AiStreamOpenRequest.knowledgeBaseIds in src/shared/ai/transport/stream.ts, AiBaseRequest.knowledgeBaseIds in src/main/ai/types/requests.ts, and the file header in src/main/ai/tools/knowledgeLookup.ts) are all reworded to match resolveKnowledgeBaseIds's actual either/or rule: the assistant's own bound bases take precedence when non-empty (request ids are then ignored entirely, not merged); only when the assistant has none does the per-turn selection define the scope. All three now use consistent wording so there's a single phrasing to reconcile, not three.

A comment-analyzer re-review caught that my first pass at two of these still opened with "Combined with ..." — leftover union phrasing that contradicted the rest of the same sentence — before I pushed; that's fixed too.

Typecheck is clean; this is a doc-only change so no new tests were needed.

@eeee0717 eeee0717 requested a review from zhangjiadi225 July 6, 2026 07:09

@zhangjiadi225 zhangjiadi225 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@eeee0717 eeee0717 added the ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval. label Jul 6, 2026

@AtomsH4 AtomsH4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was translated automatically.

LGTM

Scope: Integrate request-level knowledgeBaseIds into AI stream transport, persistent chat context, agent params, and kb_* tool registry, and use the resolved knowledge base scope uniformly during tool selection/execution; add context and registry tests.

Impact: When knowledge bases are passed manually, assistants without a bound static knowledge base will expose/execute knowledge base tools according to the request scope; assistants with a bound static knowledge base continue to use the static binding. The previous two blockers have been handled in the current head, and no new blockers/warnings were found.


Original Content

LGTM

范围:把请求级 knowledgeBaseIds 接入 AI stream transport、persistent chat context、agent params 和 kb_* tool registry,并在工具选择/执行时统一使用解析后的知识库 scope;补充上下文和 registry 测试。

影响:手动传入知识库时,未绑定静态知识库的 assistant 会按请求 scope 暴露/执行知识库工具;已绑定静态知识库的 assistant 继续以静态绑定为准。之前两个 blocker 已在当前 head 处理,未发现新的 blocker/warning。

@0xfullex 0xfullex merged commit 12ea2b1 into main Jul 6, 2026
10 of 11 checks passed
@0xfullex 0xfullex deleted the slash-kb-not-triggering-tool branch July 6, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants