Repository navigation
feat(kimi-code): add workspace Scope bindings - #2043
jackie-cqz wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
| return checks | ||
| try: | ||
| arguments = [node, str(destination / "scripts/doctor.mjs")] | ||
| arguments = [node, str(destination / "scripts/doctor.mjs"), "--cwd", str((cwd or Path.cwd()).resolve())] |
There was a problem hiding this comment.
[P2] Preserve workspace identity in doctor
When --cwd contains a symlink, the Scope command hashes the path without following symlinks, but this Path.resolve() follows them, causing doctor to query a different binding and potentially report the Server default instead of the bound Scope. Use the same path normalization as workspaceKey() when forwarding the workspace to the probe.
There was a problem hiding this comment.
修复状态(head c79f213a):本条已解除。
cli/kimi_code.py::run_kimi_diagnostics() 转发给 doctor.mjs 的路径由 os.path.abspath(...)(会先做词法 normpath,把 .. 折叠掉)改为 str((cwd or Path.cwd()).absolute()),客户端不再先行规范化;随后 workspaceKey() 的 realpathSync.native(cwd) 承担解析。于是 doctor 与 Hook/Scope 对同一 --cwd 得到同一绑定键,不再出现「doctor 查询另一个绑定、回落到 Server 默认 Scope」。
证据:tests/e2e/test_kimi_code_host.py 新增 alias/../other workspace 场景,断言 run_kimi_diagnostics(cwd=compound) 返回的 scope.checks 里的 scope_id 与 scope resolve 一致(Windows 走词法父目录,POSIX 走符号链接目标)。本机未执行该 e2e(需要真实 Server 与 Node 环境),以该 head 的 tests (3.x, e2e) 全绿为准。
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
| export function workspaceKey(cwd) { | ||
| if (typeof cwd !== 'string' || !isAbsolute(cwd)) throw new Failure('workspace_required') | ||
| return { integration: 'kimi-code', kind: 'workspace', | ||
| external_id: createHash('sha256').update(resolve(cwd)).digest('hex') } |
There was a problem hiding this comment.
[P2] 工作区绑定键不做路径规范化,别名/大小写变体会静默回落到 Server 默认 Scope
workspaceKey() 只用 resolve(cwd) 做词法规范化:不解析 junction/symlink,也不归一化盘符大小写。同一目录的两种写法得到不同的 external_id,而 resolve_binding() 在没有任何 key 命中时按 allow_default(契约默认 true)落到 Server 默认 Scope,客户端不会收到错误。
证据(probes/probe_workspace_key.mjs,Node 22.22.2 / Windows,head f1908b8e):
canonical path -> fe54eb93... | normalized: D:\...\ws-real
same path, lowercase -> 4af967a1... | normalized: d:\...\ws-real
junction alias -> 4f53860f... | normalized: D:\...\ws-alias
realpath of alias: D:\...\ws-real
resolve(binding_keys=[alias]) -> scope-default (server default Scope)
resolve(binding_keys=[lower]) -> scope-default (server default Scope)
即:用户按真实路径 bind,宿主会话以别名路径上报 cwd(Windows junction/subst、macOS 上 /tmp 指向 /private/tmp 一类),Hook 的 prepare 与 Source 采集会全部写进默认 Scope 而且不报错——这正是该特性要避免的跨项目记忆混写。两语文档写的是 "SHA-256 of the normalized absolute working directory",容易被读成路径已规范化。
同仓其他宿主取的是规范化身份:integrations/zcode/plugins/powercontext/shared/scope.mjs 用 realpathSync(并叠加 git rev-parse --show-toplevel),integrations/claude-code/plugins/powercontext/scripts/workspace_scope.py 用 Path.resolve(strict=False)。本接入是唯一纯词法实现。
建议:对已存在目录用 realpathSync.native()(失败回退 resolve)后再哈希;或至少在文档中写明"别名路径是独立身份,绑定必须使用宿主上报的原始字符串",并补 alias / 大小写变体的用例。tests/e2e/test_kimi_code_host.py 目前把"junction 与真实路径是不同身份"断言为期望行为,这等于把该行为固定下来,请确认这是有意选择。
与线程 4305454445 不是同一根因:那条指 doctor 转发 --cwd 时用 Path.resolve() 与 workspaceKey() 不一致(head 已改为 os.path.abspath,客户端内部一致);本条指 workspaceKey() 自身的身份语义。
There was a problem hiding this comment.
修复状态(head 6a514384):本条根因已解决,不再成立。
workspaceKey() 现在先 resolve(cwd)、再 realpathSync.native(...),解析失败时退回词法规范化后的绝对路径(与文档新增的 fallback 说明一致),integration / kind / external_id 的结构未变。
本机实测(Windows / Node 22.22.2,exact head 副本 source/tree-6a514384):node --test tests/plugin.test.mjs 21/21 通过;新增段落覆盖 junction 别名与同一路径全大写、全小写两种变体,三种写法都解析到同一个 Scope(scope-first),bind/unbind 走别名也命中同一条绑定。
未覆盖范围:macOS/ubuntu 上大小写敏感文件系统的差异(CI 只跑 ubuntu,且那里的别名用例走 dir 符号链接),以及 realpathSync.native 在 UNC / 超长路径上抛错时的词法回落分支。
| } | ||
|
|
||
| export async function resolveScope(settings, signal, input = {}) { | ||
| const keys = input.cwd === undefined && settings.scopeId ? [] : [workspaceKey(input.cwd)] |
There was a problem hiding this comment.
[P2] 显式 Scope 下仍强制派生工作区键,非绝对 cwd 会让 Hook 从"注入上下文"退化为"整体失败"
resolveScope() 现在只要 input.cwd !== undefined 就调用 workspaceKey(input.cwd)。settings.scopeId 已配置时这个 key 在服务端不会被用到(resolve_binding() 在 explicit_scope_id 处直接返回),却把 isAbsolute 校验变成了 Hook 的硬前置条件。
Base/Head 对照(probes/hook-cwd/probe_hook_cwd.mjs,显式 Scope + cwd: "relative/workspace",Node 22.22.2):
base cwd="relative/workspace" exit=0 injected=true
head cwd="relative/workspace" exit=0 injected=false stderr: {"event":"hook","outcome":"workspace_required"}
base cwd="D:\absolute\workspace" exit=0 injected=true
head cwd="D:\absolute\workspace" exit=0 injected=true
master 上同样的输入照常注入上下文并采集 Source;head 直接以 workspace_required 结束,prepare 与 capture 都没有执行。
触发条件是宿主上报非绝对 cwd。我不能证明 Kimi 会这样上报,因此按 P2 提出、不作阻断;但这是相对 master 的功能收窄,而显式 Scope 模式并不需要这个键。
建议:const keys = settings.scopeId ? [] : [workspaceKey(input.cwd)],并补一条"显式 Scope + 相对 cwd 仍注入"的用例。
There was a problem hiding this comment.
修复状态(head 6a514384):本条根因已解决,不再成立。
resolveScope() 改为 const keys = settings.scopeId ? [] : [workspaceKey(input.cwd)],显式 Scope 时不再派生工作区键。新增用例 explicit Scope supplies context and captures without an absolute workspace 用 cwd: 'relative/workspace'、null、undefined 各调一次 Hook,断言仍注入上下文且捕获落到 scope-test;原断言 binding_keys[0] 的用例改成 assert.deepEqual(f.requests[0].body.binding_keys, [])。本机 21/21 通过,doctor 也走同一条 resolveScope,已不再把绝对 cwd 当硬前置。
同类残留只剩 Scope 脚本一路(已另发评论),不影响 Hook 与 doctor。
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
| if (args.positionals.length !== 1 || !['resolve', 'bind', 'unbind'].includes(action) || | ||
| (action === 'bind' ? !isScopeId(target) : target !== undefined)) throw new Failure('invalid_arguments') | ||
| const input = { cwd: args.values.cwd } | ||
| const key = workspaceKey(input.cwd) |
There was a problem hiding this comment.
[P2] scope.mjs resolve 在显式 Scope 下仍强制要求绝对 --cwd
第 32 行的 workspaceKey(input.cwd) 无条件执行,且位于 loadSettings() / resolveScope() 之前。因此当安装为显式 Scope(scope_id 已配置)时,执行文档给出的 scope.mjs resolve(不带 --cwd,或传相对路径)仍会以 workspace_required 失败。这与本 head 在 shared/runtime.mjs 中改成的 const keys = settings.scopeId ? [] : [workspaceKey(input.cwd)] 不一致,也与同一提交新增的文档句「显式 Scope 解析不要求工作区键」(docs/en|zh/docs/integrations/kimi-code.md)以及 PR 正文的 "Explicit Scope resolution does not depend on a workspace key" 不一致。doctor 因为默认取 process.cwd() 不受影响,只有 Scope 脚本这一路仍把绝对 cwd 当作硬前置条件,而文档要求用 Scope 脚本的只读结果来确认当前工作区的 Scope。
证据(exact head 6a514384,探针 oceanbase-powercontext-pr-2043/probes/probe_scope_cwd.mjs;server_url 指向不可达端口以排除网络因素):
scope.mjs resolve(无--cwd)→{"status":"failed","code":"workspace_required"}scope.mjs resolve --cwd relative/workspace→ 同样workspace_requiredscope.mjs resolve --cwd <absolute>→server_unavailable,说明唯一阻塞就是 cwd 校验doctor(无--cwd)→ 直接进入网络阶段,未报workspace_required
该分支没有测试覆盖:tests/plugin.test.mjs 的夹具 scope(action, scopeId, cwd = workspace) 始终追加 --cwd。
建议只在 bind / unbind(以及没有显式 Scope 时)计算绑定键,例如先 loadSettings() 再 const key = settings.scopeId && action === 'resolve' ? undefined : workspaceKey(input.cwd),并补一条「显式 Scope + 无 --cwd 的 resolve 成功」用例。
There was a problem hiding this comment.
修复状态(head c79f213a):本条已解除。
scripts/scope.mjs 现在把 workspaceKey(input.cwd) 移入 if (action !== 'resolve') 分支,resolve 不再无条件派生工作区键:显式 Scope 下不带 --cwd 或传相对路径都能解析,bind/unbind 仍要求绝对路径。
证据:
- 新增用例
Scope script resolves explicit Scope without a workspace but binding operations still require one覆盖cwd=null与relative/workspace,断言 workspace 模式下requests为空、bind/unbind返回workspace_required。 - 本机
node --test tests/plugin.test.mjs→ 22/22 通过。 - 单行变异(把
const key = workspaceKey(input.cwd)移回loadSettings()之前)后该用例恰好转红(1 failed / 21 passed),说明回归测试确实锁住了本次修复。
|
|
||
| export function workspaceKey(cwd) { | ||
| if (typeof cwd !== 'string' || !isAbsolute(cwd)) throw new Failure('workspace_required') | ||
| let directory = resolve(cwd) |
There was a problem hiding this comment.
[P2] Resolve symlinks before collapsing parent components
When --cwd contains a symlink followed by .., resolve(cwd) collapses the parent component before native resolution, so bind/unbind targets the lexical parent instead of the actual directory and can overwrite or clear another workspace's binding. Resolve the original absolute path first, preserving it through the doctor caller, and normalize lexically only if real-path resolution fails.
There was a problem hiding this comment.
平台范围补充,避免用 Windows 结果否定该问题:该差异只在 POSIX 成立。
- WSL Ubuntu(真实 symlink):
link → real/nested,输入/tmp/pc2043probe/link/../other;先normpath再 realpath 得/tmp/pc2043probe/other,直接 realpath 输入得/tmp/pc2043probe/real/other⇒ 与线程描述一致(探针oceanbase-powercontext-pr-2043/probes/probe_symlink_dotdot.sh)。 - Windows(junction):同样形状下
resolve()+realpathSync.native与realpathSync.native(原始输入)结果相同——Win32 在接触 reparse point 之前就按词法折叠..(探针probes/probe_symlink_dotdot.mjs),因此本机 Windows 复现不出该缺陷。 - 触发条件收窄:需要
cwd自身带「符号链接分量 +..」;宿主传入的规范绝对路径通常不含..,主要来自用户手写scope.mjs --cwd/doctor --cwd。 - 覆盖情况:
kimi-code-acceptance.yml的controlled-cli矩阵为[ubuntu-latest, windows-latest](无 macOS),现有夹具用的是不带..的符号链接别名,故该分支无测试覆盖。
建议按该线程方向:先对原始绝对路径做 realpath,失败再退回词法规范化,并补一条含 .. 的别名用例。
There was a problem hiding this comment.
修复状态(head c79f213a):本条已解除,且与上一轮补充的平台范围一致。
shared/runtime.mjs::workspaceKey() 改为 realpathSync.native(cwd) 直接作用于原始输入,失败时才退回 resolve(cwd),不再先做词法 resolve() 折叠 ..。POSIX 下 link/../other 按符号链接目标解析;Windows 仍按原生语义落在词法父目录,与上一轮补充的探针结论一致。
文档与测试同步:
docs/{en,zh}/docs/integrations/kimi-code.md增加「POSIX 先跟随符号链接、再解释后续..分量;Windows 遵循原生路径语义」。tests/plugin.test.mjs新增alias-parent+alias/../workspace场景,按平台分别断言scope-first(POSIX,经链接)与scope-parent(Windows,词法父目录),并验证 bind/unbind 只影响对应身份。tests/e2e/test_kimi_code_host.py增加同名场景。
本机 Windows:node --test tests/plugin.test.mjs 22/22 通过;POSIX 分支由该 head 的 controlled-cli (ubuntu-latest) 覆盖(success)。
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
Which issue or RFC does this PR close?
Follow-up to #1988 and the Kimi integration tracked in #1963.
Rationale for this change
A Kimi home currently selects one explicit Scope for every project. Workspace mode lets the same installation use each working directory's Server-owned binding, with the Server default as the fallback.
What changes are included in this PR?
setup kimi-code --workspace-scopeand an installedscripts/scope.mjs resolve|bind|unbind --cwd ...command. Existing explicit Scope installations keep their priority, and updates preserve the installed mode.Are there any user-facing changes?
Use
powercontext setup kimi-code --source <checkout> --workspace-scope, then bind an existing Scope with the installed Scope script. Open a new Kimi session after changing the installation mode.--workspace-scopeand--scope-idare mutually exclusive.Workspace keys use the
kimi-codenamespace and a SHA-256 of the native real path of the absolute working directory. Existing directories share bindings across symlink/junction aliases and filesystem-recognized case variants; native resolution precedes lexical normalization, preserving POSIX symlink/parent-component semantics. Paths that cannot be resolved retain lexical normalization. Bind existing, accessible directories again if their resolved identity changes; bindings under other identities are not migrated automatically. Subdirectories are distinct identities. Explicit Scope resolution does not depend on a workspace key, soscope.mjs resolvemay omit--cwdin that mode; bind/unbind always require an absolute workspace.doctor kimi-code --cwd ... --jsonreports current Scope selection without requiring runtime history. Workspace history describes the Scope used at the time of the attempt, rather than re-resolving later binding changes.The integration uses the existing Server contract. Scope creation and session lifecycle behavior are unchanged.
How was this change tested?
node --test integrations/kimi-code/plugins/powercontext/tests/plugin.test.mjs: 22 passed on Windows and Linux, including shared alias/case-variant bindings, parent components after symlinks, explicit Scope injection/capture without an absolute workspace, and explicit Scope-script resolution without--cwd.pytest tests/test_cli_kimi_code.py tests/e2e/test_kimi_code_host.py: 18 passed on Windows (Python 3.11.15) and isolated Linux (Python 3.12.15), using actual Kimi 2.1.1 and Node 24.21.0. The CLI loaded the managed plugin and performed native MCP save/read/search against a real Server/SQLite. Regression scenarios verify shared bindings through Windows junctions, Linux symlinks and Windows case variants, consistent doctor results, alias bind/unbind and a real CLI search launched through an alias. A symlink followed by..selects the OS-native target without overwriting or clearing the other directory's binding. Both inference services were controlled local fixtures; this was not a live-provider model run.ty check, full Linuxty check, Ruff anduv lock --checkpassed.prek run -apassed on Windows with the type hook covered by the separate full Linux type check.git diff --cached --checkpassed.AI usage statement
OpenAI Codex (GPT-6) assisted implementation, documentation, tests and validation.