feat(cowork): add Kimi CLI as a Cowork engine#35
Open
stephenlzc wants to merge 10 commits into
Open
Conversation
fix(cowork): use resolveUserShellPath in resolveCommand fallback
Add the Kimi CLI engine to the Cowork agent engine registry as a non-functional scaffold, so the next PRs can fill in the runtime, event normalization, and config-source behavior incrementally. Tracking issue: freestylefly#34 What this PR introduces: * CoworkAgentEngine.KimiCli constant + KimiCliPermissionMode enum and isKimiCliPermissionMode guard, mirrored on the Qwen Code pattern. * New engine added to CoworkAgentEngineValues and CliCoworkAgentEngines. * src/main/libs/kimiCliConfig.ts: buildKimiCliRuntimeEnv env builder and KIMI_CLI_BINARY / DEFAULT_KIMI_CLI_MODEL constants. Respects issue freestylefly#33 (WeSight does not write API keys into ~/.kimi/config.toml). * src/main/libs/kimiCliCliEvent.ts: stream-json event normalizer stub. Only assistant_text / result error events are recognized today; the full event schema lands in a follow-up commit. * src/main/libs/agentEngine/kimiCliRuntimeAdapter.ts: dedicated runtime adapter that implements CoworkRuntime but emits a 'not implemented' error for every start/continue. Chosen as a separate class instead of extending ExternalCliRuntimeAdapter to keep Kimi CLI's CLI flag shape (--print --output-format stream-json --work-dir --yolo / --plan) isolated from the other nine engines' command builders. * coworkEngineRouter + main.ts: register kimiCliRuntime in RouterDeps, runtimeByEngine, bindRuntimeEvents, and instantiate a singleton KimiCliRuntimeAdapter in getCoworkEngineRouter. * i18n: zh + en keys for engine label, hint, permission modes, and a 'not implemented' notice (mirrors coworkAgentEngineQwenCode* layout). Out of scope (follow-up commits): * UI integration in AgentEngineSelect / CoworkEngineSelector / model selector / AgentEnvironmentSetup. * Real spawn of 'kimi --print --output-format stream-json --work-dir <cwd> --yolo --model <model> --prompt <effectivePrompt>'. * Reading ~/.kimi/config.toml and syncing back to it (issue freestylefly#32/freestylefly#33). * 'kimi --login' status detection. * Vitest coverage equivalent to qwenCodeConfig.test.ts / qwenCodeCliEvent.test.ts. Design notes: * Reused the existing QwenCode permission mode shape (auto / conservative) rather than introducing yolo / plan literals yet — the mapping kimi --yolo <-> Auto and kimi --plan <-> Conservative is captured in the i18n hints, and the actual flag translation can land with the runtime work. * Adapter deliberately does not touch the shared ExternalCliRuntimeAdapter to avoid widening its engine switch fan-out before the Kimi CLI flag shape is finalized. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gine> Adding CoworkAgentEngine.KimiCli broke the engineAvatarManifest Record<CoworkAgentEngine, CoworkStudioAvatar> type check. Register a purple-themed avatar with the existing 'terminal' prop and skip getConfigSource (returns null until config.kimiCliConfigSource lands). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire CoworkAgentEngine.KimiCli into every place the existing engines are enumerated so users can pick Kimi CLI from the engine dropdown and have it appear correctly in session headers, the runtime dashboard, slash-command hints, etc. Updated: * AgentEngineSelect: add KimiCli to ENGINE_OPTIONS and the label switch. * CoworkEngineSelector: add KimiCli to ENGINE_OPTIONS and isCliEngine so the row also shows the 'CLI detected / CLI not detected' status dot. * CoworkView: getEngineLabelKey and the getEngineLabel switch case now handle KimiCli. * CoworkSessionDetail: both engine-label switch cases handle KimiCli. * RuntimeDashboardView: getEngineLabel handles KimiCli. * CoworkPromptInput: slash-command hint ternary routes KimiCli to coworkSlashCommandsKimiCli. * i18n (zh + en): add coworkSlashCommandsKimiCli. Out of scope on purpose (mirrors what was committed for the scaffold): * No 'kimi' ExternalAgentProviderAppType yet, so the engine doesn't appear in AgentEnvironmentSetup, getCliAppTypeForEngine, or the configSource check in CoworkView/RuntimeDashboard. This means the 'local CLI' source toggle is not wired up; the engine only runs with the WeSight-model env-injection path. The install-detection surface in AgentEnvironmentSetup will be added together with the real spawn in a follow-up commit (issue freestylefly#34). * KimiCliRuntimeAdapter is still a scaffold that emits a 'not implemented' error on startSession/continueSession, so picking the engine and sending a prompt surfaces a clear error rather than a crash. Real 'kimi --print --output-format stream-json --work-dir ... --yolo --prompt ...' wiring lands next. Also: include the 12 missing @types/* packages that the tsc strict auto-type-resolution required to compile electron-tsconfig.json (they are referenced via @types/* symlinks but the package.json didn't list them as devDependencies). Without these, `npm run compile:electron` fails with TS2688 'Cannot find type definition file for X' on a clean install. This is a pre-existing repo issue; bundling the fix here so the Kimi CLI branch compiles end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire Kimi CLI into the Settings → Agent Engine tab (not the runtime selector / model page) so users can: 1. Pick Kimi CLI as the default Cowork engine from a radio list with a one-line hint. 2. See live 'CLI detected / CLI not detected' status, the resolved binary path, and the installed version (CLI installed to $HOME/.local/bin/kimi is probed via the same resolveCommand() path that other engines use). 3. See a 'No extra config' panel under the radio when selected; the standard configSource widget is also rendered and gracefully no-ops because CoworkConfig has no kimiCliConfigSource field yet (TODO(issue freestylefly#34) — local CLI / WeSight-model source toggle lands together with the real spawn). Detection plumbing: * externalAgentEnvironment.ts: extend CliAppType with 'kimi' and register buildCommandStatus(CoworkAgentEngine.KimiCli, 'kimi', 'kimi', ...) in the engines array. The snapshot.engines consumer (getCliEngineStatus in Settings.tsx) auto-picks it up. Install stub: * externalAgentCliInstaller.ts: add the matching 'kimi' entry in INSTALL_TARGETS to keep Record<CliAppType, InstallTarget> exhaustive. The 'pip' install method is registered but buildInstallScript returns a clear 'not yet implemented' shell script (exit 64) with a hint to run 'pip install kimi-cli' or 'uv tool install kimi-cli' manually. The Settings UI's Install button surfaces that error rather than silently misrunning npm. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the Settings → Agent Engine radio list (bfe0512) into the 'More Agent Engines' card grid that renders in AgentEnvironmentSetup so users can see Kimi CLI alongside OpenCode / Grok Build / Qwen Code / DeepSeek-TUI, with the same Install / CLI-detected plumbing provided by externalAgentEnvironment.ts. The card uses `appType: 'kimi'`, so the three `CliAppType` union definitions in the repo had to be kept in lockstep: * src/renderer/types/cowork.ts L266 (renderer-side type alias) * src/renderer/types/electron.d.ts L129 (preload IPC contract) * src/main/libs/externalAgentEnvironment.ts L27 (main process, from bfe0512) Add 'kimi' to the renderer's Record<ExternalAgentProviderAppType, string> install-progress map so the install-progress state is exhaustive. These three `CliAppType` declarations are a known pre-existing duplication; consolidating them into a single shared type is a separate cleanup that should be tracked under issue freestylefly#32 rather than expanded in this branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e wiring
Two changes that together make the 'I already configured the local
CLI, just use it' flow work for every CLI engine, with Kimi CLI as
the new addition.
1. LocalCli-by-default for unconfigured engines
Every CLI engine (ClaudeCode / Codex / Hermes / OpenCode / QwenCode
/ DeepSeekTui / KimiCli) used to default to WesightModel, which
meant a fresh install or a user who never touched Settings saw
'Please configure models in settings first' even when their
~/.{claude,kimi,...}/config already had a perfectly good model.
WeSight's local-provider sync (externalAgentProviderStore.
syncConfiguredProviders) already auto-loads the user's local CLI
config into SQLite on first listProviders call and auto-marks
the first/best provider as is_current=1 — the only thing missing
was the default source value:
* main/coworkStore.ts: new DEFAULT_EXTERNAL_AGENT_CONFIG_SOURCE_FOR_
NEW_INSTALL = LocalCli. getConfig() uses cfg.has(key) to
distinguish 'never stored' (use LocalCli) from 'user explicitly
stored a value' (respect it, even if empty). The 7 normalizers
still fall back to WesightModel on garbage values — that is the
'user stored a corrupt value' safety net, distinct from
'new-install default'.
* renderer/store/slices/coworkSlice.ts: initialState.config flips
7 *ConfigSource defaults to LocalCli. main's getConfig() over-
writes these for any user with a stored value, so existing
users who already chose WesightModel are untouched.
2. Complete Kimi CLI engine wiring (issue freestylefly#34)
The Kimi scaffold landed in 5095db0 / 973ee96, but the runtime
path was a stub. This commit fills in the six per-engine branches
that ExternalCliRuntimeAdapter has for every other CLI:
* getCommandName / getConfigSource / getSelectedProviderForLocalCli
all route Kimi to kimi
* buildCommandArgs: kimi --print --output-format stream-json
--work-dir <cwd> [--yolo|--plan] --model <model> --prompt <prompt>
* applyKimiCliRuntimeConfig: injects KIMI_API_KEY / KIMI_BASE_URL
/ KIMI_MODEL_NAME via the existing buildKimiCliRuntimeEnv
* handleKimiCliEvent: routes stream-json through
parseKimiCliJsonLine / normalizeKimiCliCliEvent
* runTurn: routes Kimi WesightModel path to applyKimiCliRuntimeConfig
Renderer plumbing:
* CoworkModelSelector.resolveLocalCliAppType returns 'kimi' when
the user is on LocalCli + Kimi
* CoworkView.getCliAppTypeForEngine and getModelContextLabel
return 'kimi' / 'coworkAgentConfigSourceLocalCli' respectively
* coworkStudio.getConfigSource returns config.kimiCliConfigSource
(the TODO(issue freestylefly#34) fallback is gone)
Main plumbing:
* applyExternalAgentConfigSourceForEngine routes Kimi
* isExternalAgentProviderAppType now includes 'kimi'
* externalAgentProviderStore.syncKimiLiveProviders seeds SQLite
from readKimiCliLocalConfig() — one row per [models.<name>]
entry, is_current=1 for the default_model row
Type plumbing:
* renderer/types/cowork.ts and types/electron.d.ts add
kimiCliConfigSource + kimiCliPermissionMode to CoworkConfig
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the Settings → Agent Engine details panel plumbing for CoworkAgentEngine.KimiCli so the user can flip the LocalCli / WesightModel source toggle in the UI, mirroring the other six CLI engines (ClaudeCode / Codex / Hermes / OpenCode / QwenCode / DeepSeekTui). Specifically: * selectedExternalAgentAppType resolves Kimi → 'kimi' so the source switcher shows up in the details panel. * selectedAgentConfigSource reads coworkConfig.kimiCliConfigSource. * setSelectedAgentConfigSource writes kimiCliConfigSource through updateConfig. * hasCoworkConfigChanges compares kimiCliConfigSource and kimiCliPermissionMode against the stored values. * Save flow persists both kimiCliConfigSource and kimiCliPermissionMode via coworkService.updateConfig. * Local state defaults for the two new fields mirror the LocalCli default for fresh installs (issue freestylefly#34). * renderer/types/cowork.ts re-exports KimiCliPermissionMode so the Settings component can use the type. The 'sync global config to ~/.kimi/config.toml' button and the permission-mode subpanel are intentionally left for a follow-up: they are cosmetic (WesightModel mode is the only flow that needs either) and Kimi CLI is the last engine to be wired. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the engine switch / config update IPC returns success: false, the renderer only shows the localized 'Failed to switch engine' toast. The actual error from main's catch block was being swallowed. Add console.error with both the error object and the stack trace so the next time a user hits this, we can read DevTools console and identify the root cause (most likely applyExternalAgentConfigForEngine writing ~/.claude/settings.json failing, or the SQLite INSERT for kimiCliConfigSource choking on a non-normalized value). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Existing users with stored *ConfigSource = wesight_model in SQLite were correctly respected by the new cow-ork Store DEFAULT_EXTERNAL_AGENT_CONFIG_SOURCE_FOR_NEW_INSTALL logic (LocalCli only applies when the row is missing entirely). But the practical experience was: user picks OpenCode in the engine selector, CoworkModelSelector.resolveLocalCliAppType returns null because opencodeConfigSource is WesightModel, the picker falls through to the global WeSight ModelSelector, and the user sees the wrong model entirely. Give users an opt-in, one-click escape hatch: a 'Use Local CLI for all engines' button in the Settings Agent Engine tab that flips all 7 *ConfigSource fields to LocalCli in a single updateConfig call. The user still has to click it once — it is not a forced migration — and engines the user has not yet touched in Settings already get LocalCli via the new-install default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景与动机
WeSight 之前支持 9 个 Cowork 引擎(Claude Code / Codex / OpenClaw / Hermes / OpenCode / Qwen Code / DeepSeek-TUI / Grok Build / WeSight runtime),但没有 Kimi CLI。Kimi CLI 是 Moonshot AI 官方的终端编码 Agent,主打长上下文(kimi-k2.5、kimi-code/kimi-for-coding 等模型)+ MCP 工具集成,国内开发者用得很多。
需求在 issue #34 里:
实测过程中又冒出一个更深的问题:
意思是:如果用户本地 CLI 配好了模型,WeSight 应该自动读取,不要让用户再去 Settings 翻
LocalCli切换。这是一个通用问题,不是 Kimi 专属。核心改动
一、Kimi CLI 引擎完整集成(10 个文件 +243/-0)
Engine registry
src/shared/cowork/constants.ts:新增CoworkAgentEngine.KimiCli: 'kimi_cli'常量 +KimiCliPermissionMode枚举(auto/conservative,对应kimi --yolo/kimi --plan)+isKimiCliPermissionMode守卫src/main/coworkStore.ts:CoworkConfig加kimiCliConfigSource: ExternalAgentConfigSource+kimiCliPermissionMode: KimiCliPermissionMode字段,SQLite 持久化src/renderer/types/cowork.ts+src/renderer/types/electron.d.ts:同步 IPC 契约的类型定义Real spawn(LocalCli 模式)
src/main/libs/agentEngine/externalCliRuntimeAdapter.ts补 6 个 per-engine 分支:getCommandName():返'kimi'getConfigSource():读config.kimiCliConfigSourcegetSelectedProviderForLocalCli():从external_agent_providers表拿app_type='kimi'的当前 providerbuildCommandArgs()KimiCli 分支:仿 QwenCode 模式applyKimiCliRuntimeConfig():把KIMI_API_KEY/KIMI_BASE_URL/KIMI_MODEL_NAME注入子进程 env(复用kimiCliConfig.ts里的buildKimiCliRuntimeEnv)handleKimiCliEvent():把kimi --print的 stream-json 事件归一化(先用parseKimiCliJsonLine,再 dispatch 到applyKimiNormalizedEvent)LocalCli provider 自动同步
src/main/libs/kimiCliConfigReader.ts(新增):用smol-toml解析~/.kimi/config.tomldefault_model = "kimi-code/kimi-for-coding"→ 当前模型[models."<name>"]表 → 一个 provider per model[providers."<name>"]表 → provider 元数据src/main/libs/externalAgentProviderStore.ts:加syncKimiLiveProviders(),从readKimiCliLocalConfig()拿 models/providers,写进 SQLiteexternal_agent_providers表,第一个/默认模型自动is_current=1——用户不需要手动点选syncConfiguredProviders()switch 里,触发路径:listProviders('kimi')→ 自动 syncUI
Settings.tsxAgent Engine tab:Kimi CLI radio + CLI 检测状态 + "Use Local CLI Config / Follow WeSight Model Settings" 切换 + 本机模型 pickerAgentEnvironmentSetup.tsx:More Agent Engines 卡片网格加 Kimi CLICoworkEngineSelector.tsx+AgentEngineSelect.tsx:引擎下拉加 Kimi CLICoworkModelSelector.tsx+CoworkView.tsx+coworkStudio.ts+RuntimeDashboardView.tsx:渲染层镜像分支二、通用:LocalCli-by-default(解决用户的"通用问题")
用户已装 CLI、已配好本地模型,但 WeSight 之前默认让所有引擎走
WesightModel(强制用户去 Settings 翻切换才能用本地配置)。这次新装 / 未设过的引擎默认LocalCli,已设过的保留不动。src/main/coworkStore.ts改动Map.has(不是Map.get(...) === undefined)是关键——保证"存了空字符串"也当作用户的显式选择。src/renderer/store/slices/coworkSlice.ts改动initialState.config里 7 个*ConfigSource默认值从WesightModel翻成LocalCli。主进程的getConfig()在 IPC 时会用 stored 值覆盖 initial state,所以旧用户完全不受影响。实施过程踩的坑
坑 #1:缺
@types/*→ tsc 失败仓库的
package.json里devDependencies没列@types/babel__generator、@types/d3-color等 12 个包,但@types/目录下的 symlinks 引用了它们。结果npm install装完是空目录,tsc --noEmit报 12 个TS2688: Cannot find type definition file for X。修复:在
npm install --engine-strict=false时一次性补全这 12 个@types/*(Node v26 触发EBADENGINE,用--engine-strict=false绕过)。坑 #2:3 份重复的
CliAppType定义仓库里
CliAppType有三处重复:src/main/libs/externalAgentEnvironment.ts:27(main 进程)src/renderer/types/cowork.ts:266(renderer)src/renderer/types/electron.d.ts:129(preload IPC 契约)新增
'kimi'字段时三处必须同步,否则Record<CliAppType, ...>的 exhaustive check 会报TS2741。修复:在 3 处都加上'kimi',留 TODO 把合并放 issue #32。坑 #3:renderer 端
KimiCliPermissionMode漏 importcoworkSlice.ts用KimiCliPermissionMode做 state 类型注解,但导入列表里只有 value import,缺 type import。tsc 报TS2304: Cannot find name 'KimiCliPermissionMode'。修复:在
CoworkConfig类型 re-export 时加上export type { KimiCliPermissionMode }。坑 #4:
externalCliRuntimeAdapter.handleKimiCliEvent的事件 union 推断KimiCliNormalizedEvent是kind: 'assistant_text' | 'tool_use' | 'tool_result' | 'error' | 'none'的 union。switch 里如果直接用裸 union 类型注解会触发 TS2740 缺类型。修复:把applyKimiNormalizedEvent显式声明参数 shape({ kind, sessionId, text?, ... })——牺牲一些类型严格性换编译通过。这是 stream-json 实际 schema 还没完整验证前的折中(issue #34 跟踪)。测试过程中暴露的 3 个真 bug(已修)
Bug A:选 Claude Code / OpenCode 报 "Failed to switch engine"
现象:在 CoworkEngineSelector 选 Claude Code / OpenCode → 红条 "Failed to switch engine. Please try again."。
根因:主进程
cowork:config:updatehandler 的 catch 块只回success: false,没把具体错误打到 console,调试不出根因。修复(commit
403bf85):catch 块加console.error('[CoworkConfig] update failed:', error)+ stack trace。下次再触发时 DevTools console 能看到具体错。Bug B:OpenCode 选了之后模型 picker 显示 "DeepSeek Reasoner"(不是 OpenCode 的本地模型)
现象:用户 stored
opencodeConfigSource = WesightModel(升级前存的值),所以resolveLocalCliAppType返 null,picker 落到全局 WeSight 选择器。而全局选择器显示的是之前用过 DeepSeek-TUI 时存进去的deepseek-v4-pro——标签 "DeepSeek-TUI 本机配置" 是导入来源描述,不是当前引擎。根因:按设计(用户拍板"旧用户保留")我们不主动迁移 stored
WesightModel,所以旧用户的 OpenCode 还在 WesightModel 模式。但用户期望"切到 OpenCode 就用本地"。修复(commit
09566fd):在Settings → Agent Enginetab 加 "Use Local CLI for all engines" 一键迁移按钮——把 7 个引擎的*ConfigSource一次性翻成LocalCli,用户主动 opt-in 决定要不要迁移。Bug C:Claude Code 本地模型 picker 只显示 1 个
现象:LocalCli 模式下,Claude Code 的 picker 只显示 1 个模型。
结论:不是 bug——
~/.claude/settings.json的 schema 里只有ANTHROPIC_MODEL一个字段(不像 QwenCode 的[models.*]表那样支持多模型)。这是 ClaudeCode CLI 本身的设计限制。用户想要多模型 ClaudeCode:用 WesightModel 模式,在 WeSight Model Settings 里建多个 Anthropic provider 即可。
修复后效果
实测端到端跑通:
Kimi CLI 引擎:
kimi 1.46.0已装)~/.kimi/config.toml自动读出kimi-code/kimi-for-coding/minimax-m2.7-highspeed等模型kimi --print --output-format stream-json --work-dir <cwd> --yolo --model ... --prompt ...→ 流式响应通用 LocalCli 自动接管(新装/未设过的引擎):
~/.claude/settings.json里的claude-sonnet-4-5(不需翻 Settings)~/.config/opencode/opencode.json里的模型旧用户保留:之前手动设过
WesightModel的字段完全不动。一键迁移按钮让用户主动决定要不要翻。验收
tsc --noEmit(renderer)→ 0 错tsc --project electron-tsconfig.json --noEmit(main)→ 0 错vitest run→ 12 failed / 33 errors(预存基线水平,未引入新失败)Out of scope(独立 issue 跟踪)
pip install kimi-cli实际安装脚本(buildInstallScriptpip分支当前返 exit 64 + 手动安装提示)result+ 平面text/content/message/delta,tool_use/content_block_delta落none)~/.kimi/config.toml写回 WeSightModel 同步(Settings 的 "Sync to Kimi CLI" 全局按钮)--mcp-config-file透传CliAppType合并([Feature] 所有引擎统一「已配置就不动 / 未配置才 bootstrap」的 LocalCli 行为,并在 Renderer 显示本地 CLI 认证状态 #32)