Skip to content

chore(cowork): Kimi CLI follow-ups (pip install / stream-json / CliAppType / button placement)#36

Open
stephenlzc wants to merge 14 commits into
freestylefly:mainfrom
stephenlzc:feature/followup-fixes
Open

chore(cowork): Kimi CLI follow-ups (pip install / stream-json / CliAppType / button placement)#36
stephenlzc wants to merge 14 commits into
freestylefly:mainfrom
stephenlzc:feature/followup-fixes

Conversation

@stephenlzc
Copy link
Copy Markdown
Contributor

背景

这是 PR #35(Kimi CLI 引擎) 推上之后的 follow-up 集。在 PR #35 落地的过程中,maintainer 可能希望拆成更小的 review 单元,所以本 PR 单独立项。

包含 4 个 commit,全部在 Kimi CLI 引擎框架内做补强:

commit 类别
10c2f3b feat(kimi): 真实 pip install kimi-cli runner
c4f9795 feat(kimi): Kimi stream-json 完整事件 schema
0e8dd07 refactor(cowork): CliAppType 合并去重
4d2d5e7 fix(cowork): "Use Local CLI for all engines" 按钮挪位置

改动细节

1. 10c2f3b 真实 pip install kimi-cli

之前 buildInstallScriptpip 分支是 stub,返 exit 64 + "not implemented" 提示。Settings → More Agent Engines 卡片里的 "Install This Agent" 按钮点了会报错。

现在实现真正的安装流程:

  • 优先用 uv tool install kimi-cli(如果 PATH 上有 uv)
  • fallback 到 python3 -m pip install --user kimi-cli(用 ensurepip bootstrap,macOS 13+ 自带)
  • 验证 ~/.local/bin/kimi 落位 + 输出 __WESIGHT_BINARY_PATH__ 让 runner 解析回结构化结果
  • python3 缺失时返 exit 65 + 清晰提示

INSTALL_TARGETS.kimi 条目不再有 TODO 注释,packageName 填 kimi-cli

2. c4f9795 Kimi stream-json 完整事件 schema

Kimi CLI --output-format stream-json --include-partial-messages 事件 schema 是 Claude Code 的超集。之前的归一化器只认平面 text/content/message/deltatool_use / tool_result / content_block_delta 全部落 none,导致 Kimi CLI 调用工具时 WeSight 不会渲染。

新的 normalizeKimiCliCliEvent 处理:

  • assistant 消息:扫 content: [...] blocks — tool_use → tool_use,text blocks 拼接 → assistant_text
  • user 消息:content: [...] blocks — tool_result → tool_result(content 兼容 string / array 两种 shape)
  • stream_event 信封:内层 content_block_delta + text_delta → 流式 assistant_text
  • result success → assistant_text replace=true
  • 不可识别的事件 → none(不破坏现有行为)

handleKimiCliEvent 在 adapter 里只负责路由,不再做事件 shape 解析。

3. 0e8dd07 CliAppType 合并去重

仓库里 CliAppType 有 3 份重复定义:

文件 行号
src/main/libs/externalAgentEnvironment.ts 27
src/renderer/types/cowork.ts 271
src/renderer/types/electron.d.ts 132

新增 'kimi' 时三处必须同步,否则 Record<CliAppType, ...> exhaustive check 报 TS2741——Kimi PR 当时是手工改的,容易漏

合并到 src/shared/cowork/constants.ts 单点定义:

export const CliAppType = {
  Claude: 'claude', Codex: 'codex', Hermes: 'hermes', OpenClaw: 'openclaw',
  OpenCode: 'opencode', Grok: 'grok', Qwen: 'qwen', DeepSeekTui: 'deepseek_tui',
  Kimi: 'kimi',
} as const;

三处 re-export 共享定义。新增 app type 现在只动 1 个文件。

4. 4d2d5e7 修 "Use Local CLI for all engines" 按钮放置

之前按钮埋在 renderAgentConfigSourceSettings() 里,函数有 if (!selectedExternalAgentAppType || !selectedAgentConfigSource) return null;——用户必须先点中一个引擎才能看到这个按钮。这违反"一键全局迁移"的设计意图。

挪到 Agent Engine tab 内容最底部(跟引擎列表同级),无论选不选引擎都渲染。功能本身没变。

验证

  • tsc --noEmit(renderer)→ 0 错
  • tsc --project electron-tsconfig.json --noEmit(main)→ 0 错
  • vitest run → 12 failed / 33 errors(预存基线,未引入新失败)
  • ✅ 手动 E2E:Kimi Install 按钮(pip 路径需 python3 ≥ 3.10) + 按钮位置可见

Out of scope

  • Kimi MCP --mcp-config-file 透传:需要 coworkRunner 把 MCP server list 透传给 adapter 写 temp JSON,跨层耦合大,放独立 issue。
  • pip install kimi-cli 的 Windows / Linux 路径:当前只测了 macOS 实现思路(~/.local/bin/kimi 落位)。
  • Bug A 根因(PR feat(cowork): add Kimi CLI as a Cowork engine #35 提的 "Failed to switch engine"):只加了 console.error 诊断,没复现到根因。

关联

stephenlzc and others added 14 commits June 2, 2026 00:14
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>
Replace the previous 'Auto-install for Kimi CLI is not implemented'
stub with a real installer that:

* Prefers 'uv tool install kimi-cli' when uv is on PATH (fast, modern,
  uses uv-managed venvs).
* Falls back to 'python3 -m pip install --user kimi-cli' with pip
  bootstrap (works on any macOS 13+ where Python ships with ensurepip).
* Verifies the binary lands at ~/.local/bin/kimi (or elsewhere on
  PATH) and emits a __WESIGHT_BINARY_PATH__ line the install runner
  parses back into a structured result.
* Errors with a clear message + exit 65 if python3 itself is missing.

This unblocks the 'Install This Agent' button for Kimi CLI in the
Settings → More Agent Engines card. The companion packageName field
'kimi-cli' replaces the empty placeholder; the install_target entry
no longer needs the TODO comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kimi CLI's --output-format stream-json is a superset of Claude Code's.
The previous normalizer only recognized flat text/content/message/delta
fields; tool_use and tool_result events landed in 'none' and never
rendered in Cowork.

The new normalizer handles:

* 'assistant' with message.content blocks: tool_use blocks → tool_use;
  text blocks → assistant_text (concatenated).
* 'user' with message.content blocks: tool_result blocks → tool_result
  (handles both string and array content).
* 'stream_event' envelope: content_block_delta with text_delta → assistant_text.
* 'result' with subtype 'success' / failure → assistant_text replace=true
  (existing behavior) or error.
* Falls back to flat text/content/message/delta for non-standard Kimi
  events.

handleKimiCliEvent in externalCliRuntimeAdapter just routes the
normalized event through applyKimiNormalizedEvent (no event-shape
handling change needed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three files had duplicate CliAppType union definitions:
* src/main/libs/externalAgentEnvironment.ts:27
* src/renderer/types/cowork.ts:271
* src/renderer/types/electron.d.ts:132

Each had to be kept in lockstep when adding new app types (Kimi was
the most recent trigger — see PR freestylefly#35 commit 973ee96). Consolidate
to a single canonical definition in src/shared/cowork/constants.ts
that all three re-export:

* constants.ts:    new CliAppType = { Claude, Codex, Hermes, OpenClaw,
                    OpenCode, Grok, Qwen, DeepSeekTui, Kimi } as const
* cowork.ts:      import the shared type, alias as SharedCliAppType,
                    keep local CliAppType name for back-compat
* electron.d.ts:   import the shared type, drop the duplicate union
* externalAgentEnvironment.ts: same pattern

New app types now require touching exactly one file
(src/shared/cowork/constants.ts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ted-engine conditional

The button was nested inside renderAgentConfigSourceSettings(),
which returns null when no engine is selected — meaning the
button was only visible after the user had already picked an
engine in Settings. That contradicts the intent of a 'one-click
global migration' action that should fire even before the user
picks anything.

Move the button to the bottom of the Agent Engine tab content
(after the engine list), so it always renders regardless of
selectedEngine. No other change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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