Skip to content

feat(kimi-desktop): add independent secondary models and inference settings - #813

Merged
yetone merged 12 commits into
magpie-community:mainfrom
lilongsky:codex/kimi-desktop-secondary-models-20261107
Nov 9, 2026
Merged

yetone merged 12 commits into
magpie-community:mainfrom
lilongsky:codex/kimi-desktop-secondary-models-20261107

Conversation

@lilongsky

@lilongsky lilongsky commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

English

Let the signed-in Kimi desktop Work agent use Magpie's external primary model and an optional secondary model. The embedded Kimi Work models page now configures each role's model, reasoning effort and optional context input budget. The ordinary Agents picker shares the same primary selection. Version remains 0.1.6; only packages/kimi-desktop changes, with no Magpie core or Kimi application-resource patch. This head requires fresh review; approval of an earlier commit does not cover it.

Model and parameter behavior

  • Kimi's native model menu remains visible, but request aliases cannot retarget external inference. Native top-level thinking, reasoning and reasoning_effort are ignored, even before new preferences are saved. An empty configured effort leaves the decision to Magpie/the provider; a fixed Magpie route effort has priority.
  • Offer only explicit catalog efforts and context limits, without model-name guessing or presenting the old 131072 runtime fallback as known capacity. Primary and secondary can have different parameters while targeting the same actual model: authenticated role profiles distinguish the roles instead of inferring them from equal IDs.
  • With no secondary, no companion is installed and no role-plugin RPC is issued; native Agent schema/type behavior is retained. With a secondary, a new Agent omitting both model and subagent_type uses it. Explicit native types without model remain native. model="primary" and model="native" remain available, and resume/fork retain their existing binding. Older authenticated profiles without a role keep their original model and use provider defaults without an extra budget; start a new child to use independent role settings.
  • Bind saved preferences to their selected model. While a model, gateway or key switch waits, active requests keep their previous in-memory role settings. Validate before the official primary selector, detect concurrent role changes, merge partial patches into the latest host document, and report partial saves accurately. Unsupported saved values remain visible for explicit replacement/reset.

Context budget and paid plugins

The context control is a bridge input budget (counted/estimated), not Kimi's native compaction window or an output-token limit. Default adds no count or extra limit. A custom positive budget cannot exceed the published catalog cap. Before generation, a supported count-only copy goes through Magpie's existing POST /v1/messages/count_tokens with the same gateway identity. Over-budget input returns context_length_exceeded; unavailable counting or unsupported input returns fixed context_count_unavailable. Neither sends generation. Counting can add latency and consume model-service request allowance; no listening endpoint or native RPC permission is added.

The plugin preserves generation messages/tool history, does not alter max_tokens or usage, and does not directly trim history. Kimi may try native compaction after overflow; summary calls face the same role budget, so fixed system/tool overhead can prevent recovery. Native compaction can lose earlier details. Raise/remove the budget, shorten the input or use a new conversation if recovery fails; increasing it does not enlarge Kimi's native window.

Kimi login, other native plugins, paid-data authorizations, billing and service routes remain intact. Financial-tool schemas, calls and results are preserved; counting uses existing tool content instead of re-executing paid tools. Regressions check official search/fetch URL, credential and body and preserve an unrelated paid-plugin record during companion enable/clear. No real financial database query was made in acceptance.

Compatibility and lifecycle

Retain the prior review fixes: opaque authenticated route tokens and key-namespaced profile names; no generated upstream branding, target IDs or reversible encoding; 30-second plugin-info throttling with busy skips; Agent-only SSE buffering with exceptional passthrough; and in-memory-only snapshots of external native values. Shared/non-identity route quotations are neutralized without binding; no-key connections skip marker checking. Invalid authenticated system/developer identity still fails closed. Bindings survive brief catalog absence and retire only after at least three successful missing synchronizations and ten minutes; failures do not count. Dotfiles are ignored/preserved; ordinary unknown files still conflict. Canonical paths support aliases/symlinks and ordinary installation without weakening ownership checks.

Clear/disconnect/stop retains disable → confirmed reload → removePlugin. Synchronous exit disables the verified companion registration on disk and retains the pending cleanup obligation. Kimi writes retain byte comparison plus temp/rename. The embedded settings launcher also retains bounded retries, localized manual Retry and recovery after an occupied port is released. No unrelated configuration is rewritten. The maintainer-requested cleanup also removes twenty leftover blank lines in role-plugin-state.mjs: nonblank lines and Bun-transpiler output are identical, and the focused suite passed 58 tests / 230 assertions. The final four-command sequence below was rerun after that cleanup.

Validation of this head

Command Actual result
HOME=<temporary HOME> bun test packages/kimi-desktop 626 pass, 0 fail, 3990 assertions; 29 files
bun scripts/check.mjs kimi-desktop Exit 0
bun scripts/check.mjs Exit 0
HOME=<temporary HOME> bun test 7447 pass, 20 skip, 0 fail, 239426 assertions; 523 files

GitHub CI for the final head also passed both test and on-npm jobs.

Run in that order on Bun 1.4.4, using source-identical copied trees, temporary HOME/XDG, synthetic credentials, the existing OS real-HOME/application deny policy, real-ps bootstrap and passive synthetic observer. No additional CLI test exclusions were added. This is a repository-default run with those explicit harness accommodations, not pristine bare-process or real-browser acceptance. Runtime/test bytes stayed unchanged afterward; only the compatibility note was updated.

Component checks produced 198 final assertion-red single-point variants: capability/policy 24, role identity 21, active/pending/legacy transitions 32, settings API/forwarding 34, page controls/recovery 50, counting 36 and empty-text normalization 1. Three affected count variants were rerun after the empty-text fix. A separate legacy-branch deletion was equivalent; replacing its default with actual effort/budget was assertion-red. This is not a claim that all deletion attempts failed. Infrastructure errors were excluded. An extracted native SDK VM probe also passed 9 scenarios / 119 assertions.

Native acceptance of this revision

On Magpie 0.1.3336 and Kimi 3.2.25 / 3.2.25.22, the official CLI installed the checked 22-file archive. It reloaded the shared Bun worker; the Magpie main PID/application snapshot stayed unchanged. Only View → Reload refreshed the WebView.

The native page showed 54 catalog models and independent capabilities. Saved primary low/196608 and secondary high/131072 through the UI; authenticated API readback returned 200 and refresh confirmed both applied, without changing selected models. In an existing owned Kimi task, the primary invoked one secondary Agent; the child used one local Read on an owned synthetic file, and child/parent returned the expected phrases. The task's native menu displayed K3 while the external primary setting retained its configured choice.

Removed only the exact test preferences with byte comparison plus temp/rename. Original model choices, all other connection fields, native model definitions/current choice, other 23 native-plugin records, other Magpie plugins, AtomCode-state digest, original project and unsent draft were preserved. No Magpie quit/start/restart, Chrome or Playwright occurred. The installed archive predates the formatting-only helper cleanup and compatibility-note update; the helper's nonblank code and transpiler output are unchanged.

The run confirms persistence and native execution, not independent provider reasoning behavior or exact tokenization. Native over-budget compaction, native same-model/different-option execution and the five quit/disconnect lifecycle cases were not repeated for this revision. Synthetic tests cover the new role/budget rules; earlier five-case native results remain limited to their dated revision. Details and the prior review evidence: COMPATIBILITY.md.

中文

让已登录的 Kimi 桌面端 Work 智能体使用 Magpie 的外部主模型和可选副模型。内嵌的 Kimi Work 模型页面现在可以分别设置两角色的模型、思考强度和可选上下文输入预算,普通 Agent 选择器共享同一个主模型配置。版本保持 0.1.6,只修改 packages/kimi-desktop,不修改 Magpie 核心或 Kimi 应用资源。本 head 需要重新审查,旧提交的批准不覆盖本次。

模型与参数行为

  • Kimi 原生模型菜单继续显示,但请求别名不能改变外部推理目标。原生顶层 thinking、reasoning 和 reasoning_effort 会被忽略,尚未保存新偏好的连接也适用。配置强度为空时交给 Magpie/供应方决定;Magpie 路由中固定的强度优先。
  • 只提供模型目录明确公布的强度和上下文上限,不按名称猜测,也不把原有 131072 运行时回退值显示为已知能力。主、副使用同一个实际模型也能采用不同参数:认证角色 profile 区分角色,不通过相同模型 ID 推断。
  • 未选副模型时不安装伴随插件、不发角色插件 RPC,保留原生 Agent schema/类型行为。选择副模型后,同时省略 model 和 subagent_type 的新 Agent 默认使用副模型;明确原生类型且省略 model 时仍保留原生类型。继续支持 model="primary" 和 model="native",resume/fork 保留原绑定。旧的无角色认证 profile 保留原模型,使用供应方默认且不增加预算;新建子任务才采用独立角色参数。
  • 偏好绑定所选模型。模型、网关或密钥切换等待应用时,活动请求继续使用内存中的旧角色参数。调用官方主模型选择器前校验参数,检测角色并发变化,将部分字段合并到最新 host 文档,并准确报告部分保存。不可用的已保存值仍显示,允许明确替换或重置。

上下文预算与付费插件

上下文控件是桥接输入预算(计数/估算),不是 Kimi 原生压缩窗口或输出长度限制。默认不增加计数或额外限制;自定义正整数预算不能超过目录公布的上限。生成前,支持格式的计数副本以相同网关身份请求 Magpie 已有的 POST /v1/messages/count_tokens。超预算返回 context_length_exceeded;计数不可用或输入格式不支持时返回固定 context_count_unavailable,两种情况都不发送生成请求。计数可能增加延迟并占用模型服务请求额度;不新增监听端点或原生 RPC 权限。

插件保留生成消息和工具历史,不改 max_tokens 或 usage,也不直接裁剪历史。Kimi 超限后可能尝试原生压缩,但摘要请求同样受角色预算约束,固定 system/工具开销可能阻止恢复。原生压缩可能丢失早期细节;失败时需提高或取消预算、缩短输入或新建会话,提高预算并不会扩大 Kimi 原生窗口。

Kimi 登录、其他原生插件、付费数据授权、计费及服务通路保持不变。金融工具的 schema、调用与结果保留;计数使用已有工具内容,不重新执行付费工具。回归测试核对官方 search/fetch 地址、凭据和 body,并在启用/清空伴随插件时保留无关付费插件记录。验收没有执行真实金融数据库查询。

兼容性与生命周期

保留此前修复:不透明认证 route token、按密钥分命名空间的 profile 名;生成上游内容不含品牌、目标 ID 或可逆编码;getPluginInfo 30 秒节流与忙碌跳过;SSE 仅缓冲 Agent 调用、异常透传;外部原生值仅在内存快照。共享指令和非身份位置的标记引用会中和而不绑定,无密钥连接不检查标记;无效 system/developer 认证仍拒绝。短暂目录缺席保留绑定,至少连续三次成功同步缺席且超过十分钟才退役,失败同步不计数。点文件忽略并保留,普通未知文件仍冲突。规范路径兼容别名/符号链接和普通安装,所有权检查不放宽。

清空、断开和 stop 保持“禁用 → 确认 reload → removePlugin”;同步退出在磁盘禁用经核验的伴随记录,并保留待清理义务。Kimi 写入继续使用原字节比较和 temp/rename。内嵌设置 launcher 保留有界重试、本地化手动重试和端口占用解除后的恢复。不改写无关配置。作者要求的空行整理也已完成:role-plugin-state.mjs 删除20个遗留空白行,非空行和 Bun 转换输出完全一致,相关单测通过58项/230断言。下面的最终四命令已在整理后重跑。

本 head 的验证

命令 实际结果
HOME=<temporary HOME> bun test packages/kimi-desktop 626 通过、0 失败、3990 断言;29 个文件
bun scripts/check.mjs kimi-desktop exit 0
bun scripts/check.mjs exit 0
HOME=<temporary HOME> bun test 7447 通过、20 跳过、0 失败、239426 断言;523 个文件

最终 head 的 GitHub CI 也已通过 test 和 on-npm 两项。

在 Bun 1.4.4 上依次运行,使用源字节相同的副本、临时 HOME/XDG、合成凭据、原有 OS 真实 HOME/应用禁止访问策略、real-ps bootstrap 和被动合成 observer。未增加 CLI 排除项。这是带上述显式 harness 的仓库默认测试,不是 pristine bare 进程或真实浏览器验收。此后运行代码和测试字节未变,只更新兼容性文档。

组件自检共 198 个最终单点变体得到断言红灯:能力/策略 24、角色身份 21、活动/等待/legacy 切换 32、设置 API/转发 34、页面控件/恢复 50、计数 36、空文本规范化 1。空文本修正后重跑了 3 个受影响的计数变体。另一次删除 legacy 分支等价,改成实际强度/预算后产生断言红灯;没有声称所有删除尝试都失败。环境错误未计入。提取的原生 SDK VM 探针另通过 9 场景/119 断言。

本修订的原生验收

在 Magpie 0.1.3336 与 Kimi 3.2.25 / 3.2.25.22 上,官方 CLI 安装了已核验的 22 文件包。仅共享 Bun worker 重载,Magpie 主进程 PID/应用快照保持;随后仅 View → Reload 刷新 WebView。

原生页面显示 54 个目录模型及独立能力。通过 UI 保存主 low/196608、副 high/131072,认证 API 回读 200,刷新后两组均已应用,所选模型未变。在已有自有 Kimi 任务中,主代理调用一次 secondary Agent,子代理只用一次本机 Read 读取合成文件,主副返回预期短语。该任务原生菜单显示 K3,外部主模型设置仍保留配置选择。

以原字节比较和 temp/rename 仅清除精确测试参数。原模型选择、其余所有连接字段、原生模型定义/选择、其他 23 个原生插件记录、其他 Magpie 插件、AtomCode 校验值、原项目和未发送草稿均保留。没有退出/启动/重启 Magpie,没有启动 Chrome 或 Playwright。安装包早于 helper 纯空行整理和兼容性文档更新,helper 非空代码及转换输出未变。

本轮证明保存和原生执行成功,未独立测量供应方思考行为或精确 tokenization。未重复原生超预算压缩、原生同模型不同参数或五项退出/断开生命周期验收;新角色/预算逻辑由合成测试覆盖,先前五项原生结果仍仅限对应日期的修订。详情及此前审查证据见 COMPATIBILITY.md。

Add a secondary-model preference and embedded plugin settings page while
keeping the primary model under the existing Magpie agent connection.
Reapply the native secondary experiment after runtime regeneration,
confirm idle-session reloads, retry busy sessions, and restore owned fields.

Validation: 77 isolated desktop tests passed with 704 assertions; the target
registry check and offline npm packaging passed. An earlier candidate
completed two native Work subagents on
official macOS Kimi and Magpie, with Sol/Astra routes HTTP 200. The final
enabled-baseline inheritance correction has isolated regression coverage;
the compatibility record distinguishes it from the earlier live check.

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

Request changes.

Thanks for this. The design is careful. Ownership of the two runtime scalars is kept in state.secondary, so values another writer installed are never restored over. Writes are compare-then-rename. A TOML shape the module doesn't understand fails closed. The settings server checks Host, Origin and a per-instance token. The whole repo suite is green with this branch. A few things need to change before it can merge:

  1. A failed session reload leaves Magpie's alias in Kimi's config after disconnect. I ran it with the PR's own fixture() harness, using regenerate: false and a native file of [secondary_model] model = "native/first" with secondary-model = false. reloadActiveSessions reports a session as skipped-active-turn, and then kimiPlugin.ReloadSession throws. After that, the user disconnects in Magpie. The primary base URL goes back to agent-gw.kimi.com, but the runtime TOML keeps model = "magpie-local-…" and secondary-model = true, and the status stays secondary_model_reload_failed. secondaryPending is never cleared, so retrySecondarySessions throws on every tick, and reconcileSecondary(runner, '') on disconnect throws before it restores the baseline. Only the synchronous exit restore would put the user's values back. Kimi's subagents are left pointing at an alias Magpie no longer serves. A retry that keeps failing should give up on that session, or skip it, so that the restore still runs. Please add a test for this case. This matters more because COMPATIBILITY.md says kimiPlugin.ReloadSession was only tested synthetically.

  2. A secondary-model failure blocks the primary connection. Take a user with a saved secondaryModel whose runtime/kimi-code/config.toml doesn't exist (yet). On connect, protectNativeSecondary → captureSecondaryBaseline → readNativeSecondary throws ENOENT unwrapped. The tick then ends as adapter_failed, and the base URL is never changed: the probe saw adapter_failed, with base still https://agent-gw.kimi.com/coding/v1. reconcileSecondary already maps ENOENT to secondary_model_unavailable. Baseline capture should do the same, or defer, so that the optional feature never stops Kimi from connecting at all.

  3. The settings page doesn't map the codes the adapter emits. settings.html has secondary_model_unsupported, but the adapter emits secondary_config_unsupported (secondary-config.mjs:10, adapter.mjs:28). secondary_model_reload_failed, adapter_failed and invalid_connection have no text either. All of these show as raw codes.

  4. The page is Chinese only. <html lang="zh">, and every label and message is Chinese, while the package page title has an English title. Please add English text and pick the language the way the other plugin pages do, from the host's language. The page also uses a native <select> and a coloured #6266d6 submit button. Magpie's own UI avoids both, so please use a plain button list (or radio rows) and the neutral button style.

  5. Two guards have no test. Breaking each by hand left the package suite green (80 of 80):

    • commitNativeSecondary's byte comparison (if (!fs.readFileSync(...).equals(bytes)) turned into if (false)). Add a test that rewrites the file between the read and the commit, and expects kimi_config_changed with the other writer's bytes kept.
    • The rpc.mjs allow-list. Removing kimiPlugin.reloadActiveSessions and kimiPlugin.ReloadSession from it changes nothing, because every test injects rpcCall. A small test that the real rpcCall path accepts these two names, and still rejects others, would catch a typo. That matters here because the names (ReloadSession, with a capital R) haven't been checked against a live Kimi.
  6. Smaller items (fix or say why not):

    • Rewriting the runtime TOML through Bun.TOML.stringify drops comments and turns 1.0 into 1 (a float becomes an integer). Integers above 2^53 make Bun.TOML.parse throw, so such a file is reported as secondary_config_unsupported. That is acceptable since Daimon regenerates the file, but please say so in COMPATIBILITY.md.
    • The settings server started in KimiDesktop() is never closed in adapter.stop(). A changed settingsPort option only takes effect after the host restarts.
    • stop() reconciles with '' and can wait out the 30s RPC timeout.
    • The registry.json summary for kimi-desktop doesn't mention the secondary model.

What was run (head a1f9919, merge-base = origin/main 1975c93, Bun 1.4.2 on a macOS test machine, HOME=$(mktemp -d) for every run):

  • bun test packages/kimi-desktop: 77 pass, 0 fail (704 expect() calls, 7 files).
  • bun scripts/check.mjs kimi-desktop: ✓ registry.json, exit 0. check.mjs skips agent packages, so this checks only the registry.
  • Whole repo bun test: 6546 pass, 20 skip, 0 fail, 475 files, exit 0.
  • Probe tests in the PR's fixture() harness (deleted afterwards): items 1 and 2 above. Also, a secondary model hidden in Magpie after it was applied: the alias is removed and the status is secondary_model_unavailable, which is fine.
  • Guards broken by hand, one at a time, then reverted:
    • restoreTarget always using the baseline: 3 tests red.
    • The token check: 2 red.
    • The Host check: 1 red.
    • The Origin check: 1 red.
    • The catalog allow check in saveSettings: 1 red.
    • The commit byte check: 0 red.
    • The rpc allow-list: 0 red.
  • A TOML round-trip probe through Bun.TOML: comments dropped, 1.0 → 1, ints above 2^53 rejected. inf/nan, dates and quoted dotted keys survive.
  • Read every changed file. The only network use is the existing loopback /v1/models, the loopback settings server and the Kimi control websocket. There is no eval and no write outside ~/.config/magpie-kimi and Kimi's runtime config.toml. The settings HTML is not CORS-readable, and the API leaks only model ids.

Not tried: a real Kimi Desktop (not installed on any test machine here), Windows and Linux, and the settings page inside Magpie's GUI.

Use the official host CLI for primary selection and retain secondary-only
patches. Add host-language English/Chinese radio rows and confirmed applied
IDs, fix disconnect restoration after failed reloads, keep optional TOML
failures from blocking the primary, and close/rebind the settings service.

Cover native compare-before-write and both real RPC allow-list paths.
Record TOML normalization and the unsupported Daimon 1.0.6 V2 boundary.

Validation: 113 tests, 795 assertions; registry check; offline package with
15 source-matched files and 12 runtime/UI files matching the installed
candidate. Native Magpie primary selection Sol -> Astra -> Sol agreed with
host applied/stash/connection state, without submitting Work inference.
@lilongsky lilongsky changed the title kimi-desktop: configure a secondary model for native Work subagents kimi-desktop: configure primary and secondary models in Work Nov 6, 2026

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

Request changes.

Thanks. Every point from the last review is fixed, and each fix has a test that goes red when the fix is taken out (listed below). What is left is mostly about the new primary-model save in b5f4621:

  1. The parent process is run without checking that it is Magpie. setPrimaryModel takes whatever ps -o comm= (macOS) or /proc/<ppid>/exe (Linux) names and runs it with kimi-desktop model magpie/<id>. Magpie starts the host's Bun directly today, so the parent is Magpie. But if the host is ever started through a shell, a wrapper, or another app, that program gets these arguments: sh kimi-desktop model … would run a file named kimi-desktop in the working directory. Please only accept a discovered parent (and a which result) whose basename is Magpie's (magpie…, case-insensitive, .exe on Windows), and add a test where the parent is /bin/sh and the result is primary_model_unavailable. An explicit magpiePath can stay as the user's choice.

  2. On Windows only PATH is tried. There is no parent lookup on win32, and the Windows app is usually not on PATH, so on the platform this plugin was first verified on, saving a primary model probably returns primary_model_unavailable until the user sets magpiePath. Please look up the parent there too (for example, Get-CimInstance Win32_Process -Filter "ProcessId=<ppid>" → ExecutablePath), or say in the README and on the page that Windows needs magpiePath. If you have run this save on Windows, please add it to COMPATIBILITY.md.

  3. On Kimi 3.2.25 / Daimon 1.0.6 the page would say "applied" when nothing is applied. COMPATIBILITY.md says V2 reads runtime/kimi-code/home/config.toml and ignores secondary-model. Both reload RPC names are still there, so the adapter writes the legacy file, gets its reload acknowledged, and reports the secondary model as applied. Please detect that layout (for example, from the Daimon version in the runner file, or because the converted home/config.toml exists) and report a fixed code such as secondary_model_unsupported_runtime instead of applying. Add a test, and give the code a string on the page.

  4. Three guards still pass the suite when they are broken by hand (113 of 113 green each time):

    • saveSettings' catalog check for primaryModel (hasPrimary && !catalog.some(...) → false). A primary id that is valid but not in the gateway catalog then goes to the host command. Please add a test that expects model_not_enabled_for_kimi and no setPrimaryModel call.
    • The sameGateway(fresh) check after the primary save (if (!sameGateway(fresh)) fail(hasSecondary ? …) → if (false)). Please add a test where the host call changes the gateway key, and expect settings_partially_saved with a secondary and connection_changed without one.
    • this.secondaryReloadPending = true in the full-disconnect block. Removing just that line leaves the restored baseline unconfirmed, and no test notices. Please assert that a reload of the baseline is requested after disconnect.

Not blocking: once disconnected, a baseline reload that keeps failing throws on every tick. The file is already restored by then, so this is only log noise. Capping it, or backing off, would be nice.

What was run (head b5f4621, merge-base = origin/main, Bun 1.4.2 on a macOS test machine, HOME=$(mktemp -d) for every run):

  • bun test packages/kimi-desktop: 113 pass, 0 fail (795 expect() calls, 10 files).
  • bun scripts/check.mjs kimi-desktop: ✓ registry.json, exit 0.
  • Whole repo bun test: 6582 pass, 20 skip, 0 fail, 478 files.
  • Guards broken by hand, one at a time and reverted. These went red:
    • the disconnect clear: 2 red;
    • the baseline catch: 2;
    • the commit byte check: 1;
    • each RPC allow-list name: 1;
    • the secondary catalog check before the host call: 2;
    • the patch key allow-list: 1;
    • the skip-adapter env check: 1;
    • setting the skip-adapter env: 2;
    • closing the settings server on stop: 1;
    • the settingsPort rebind: 1.
  • The three in item 4 stayed green.
  • Read every changed file. The only new process is the host command above.

Not tried: a real Kimi Desktop or a real magpie kimi-desktop model call (no Kimi on the test machines), Windows and Linux, and the page inside Magpie's GUI.

Accept only Magpie-named executables from automatic parent and PATH
discovery, and discover Windows parents through PowerShell CIM. Keep
explicit executable paths available for custom names.

Report the converted V2 secondary layout as unsupported without blocking
the primary bridge or restoration of owned legacy settings. Hide stale
application markers and explain the status in English and Chinese.

Cover catalog validation, gateway changes during primary saving and the
final disconnect baseline reload with mutation-sensitive behavior tests.

Validation: 128 tests, 886 assertions; registry and offline package checks.

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

Verdict: request changes at 536617a

Summary

The two new commits (b5f4621, 536617a) address every blocking point of the last review (pr813.md), and each fix has a test that goes red when it is broken by hand. But b5f4621 changed stop() so that it no longer puts Kimi back on its own model and menu, which main does. That is a new regression and blocks the merge.

Blocking: stop() leaves Kimi on a Magpie alias with nothing to clean it up

  • adapter.mjs:909 stop() now calls this.restore({ reload: false }). In restore() (adapter.mjs:415-447) reload: false skips the whole RPC block: getKimiModelList, setModel back to the native model, updateKimiModelList without the magpie-local-* aliases, and the final setModel. It then sets state.active = false and saves it.
  • Main's stop() calls restore() with reload, so these RPCs run.
  • Probe (on mote, the same script in both trees): connect with tick, set config model.current to a magpie-local-… alias, have getKimiModelList return the native model plus the alias, then stop().
    • origin/main 090b46b: getKimiModelList, setModel{k2d8-preview}, updateKimiModelList (alias gone), setModel{current}.
    • PR head: no RPC at all. config.json keeps model.current = magpie-local-…, the menu keeps the aliases, and state.active is false.
  • Result: after the plugin is turned off or magpie quits, Kimi is pointed at its official gateway with a Magpie alias as its selected model. The next start does not repair it. isMenuAlias is used only inside restore(), which returns at once when !state.active (adapter.mjs:412). The sync exit path (restoreFileOnExit) keeps active for this reason; the async path drops it.
  • The test "stop restores the disk baseline without new RPCs and keeps recovery ownership" (secondary-model.test.mjs:865) and README.md:87 both describe this as intended. The test checks the TOML and the base URL but not model.current or the menu.
  • Fix: on stop, skip only the secondary-session reload (reloadActiveSessions/ReloadSession) and keep the primary model and menu RPC restore that main does, with a short timeout if shutdown time is the concern. Or, if no RPC may run on stop, keep state.active and the model backup so the next start finishes the restore. Add a test that stop() leaves model.current native and the menu free of magpie-local-*.

Prior points: verified by hand breaks (mote, one at a time, each reverted)

Break Red
Disconnect clear block disabled 3
Baseline ENOENT rethrown 2 ("missing/unsupported optional secondary TOML does not block the primary bridge route")
settings.html code key renamed 2 (en + zh "fixed adapter codes have local text")
html lang forced to zh / boot lang forced to zh 9 / 2
Commit byte check disabled 1 ("refuses stale bytes")
RPC allow-list typo: ReloadSession / reloadActiveSessions 1 / 1
V2 converted-config guard disabled 3
stop() not closing the settings server 1
Child skip env set to '0' / index skip env removed 3 / 1
Symlink basename check removed 1
Primary catalog check removed 1
Primary validModelID check removed 0: redundant with the catalog check, fine
settings_partially_saved on secondary failure replaced with the plain code 0: that path has no test (minor, please add one)

Read of every changed line

  • Writes to the real Kimi config: only through the adapter's existing paths under the configured Kimi home. Tests use temp dirs; the settingsPort test injects a fake adapter into globalThis first.
  • Network: no new URLs or fetch. The settings server is the existing loopback one. primary-model.mjs runs <magpie> kimi-desktop model magpie/<id> through execFile (no shell), with MAGPIE_KIMI_SKIP_ADAPTER=1, timeouts 6s/60s. The exe comes from the parent PID, then PATH, then magpiePath; a symlink named magpie that points elsewhere is refused. The magpie CLI path magpie <agent> <field> <value> exists (internal/cli/main.go ~601).
  • Credentials: none read or sent. Prompt or instruction-shaped text: none added.
  • settings.html sets all text with textContent; radio rows, not a native select.
  • Minor: the path-guidance line names only Windows. COMPATIBILITY.md records a real macOS run (Kimi 3.2.17, Magpie 0.1.3077); there is no real Windows or Linux run.

Version

main has 0.1.5. dist index.json has kimi-desktop 0.1.0 to 0.1.5. npm returns E404 (not published there). #813 is the only open kimi-desktop PR, so 0.1.6 is the next free version. OK.

Run (merged head d5823a26 = origin/main 090b46b + PR, Bun 1.4.2, HOME=$(mktemp -d))

  • mote: bun test packages/kimi-desktop 128 pass, 0 fail (886 expects, 10 files); bun scripts/check.mjs kimi-desktop exit 0; bun scripts/check.mjs exit 0 (48 ✓, no ✗); root bun test 6733 pass, 20 skip, 0 fail (485 files).
  • WSL magpie-desktop: package 128 pass; both checks exit 0; root bun test 6734 pass, 19 skip, 0 fail.

中文总结

上轮 5 个阻塞点及小问题都已修复,逐一手动破坏后对应测试均变红(仅主模型 validModelID 检查与 catalog 检查重复,以及 secondary 失败时的 settings_partially_saved 无测试,属小问题)。版本 0.1.6 是下一个可用号。mote 与 WSL 上包测试、两种 check.mjs、根 bun test 全部通过。
但新提交 b5f4621 让 stop() 以 reload:false 调用 restore,跳过了 main 会做的 getKimiModelList / setModel / updateKimiModelList:插件停用或 magpie 退出后,Kimi 回到官方网关,却仍选着 magpie-local-* 别名,菜单里也留着别名,而 state.active 已被置 false,下次启动不会再修复。探针在 main 与 PR 上对比确认。需要在 stop 时保留主模型/菜单的 RPC 恢复(只跳过 secondary 会话 reload),或保留 active 让下次启动收尾,并补测试。结论:request changes。

Use existing model aliases and role descriptions in Daimon 1.0.6's
converted runtime model pool. Confirm idle cached-session reloads,
reconcile regenerated files, and preserve native and external ownership.
Keep legacy runtime support and expose accurate bilingual page status.

Restore the primary selection and menu during explicit stop while
skipping extra secondary-session reloads. Cover secondary write failures
after a successful primary save without rolling that save back.

Validated with 165 package tests, 24 extracted-core contract checks, and
native Kimi 3.2.25 / Magpie 0.1.3208 primary, secondary and restore turns.

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

Verdict: request changes at 30616f7

Summary

30616f7 fixes the last review's blocker. stop() again calls the normal restore() (adapter.mjs:1167), so Kimi gets back its native model.current and a menu with no aliases, the same as main. Two new tests go red when it is turned back to reload:false. The new V2 model-pool support is careful. It owns only secondary_model.models and default_model, keeps model and provider definitions out of state, compares bytes before a temp+rename write, keeps an external writer's pool, and leaves a user who never picked a secondary untouched. Most of its guards have tests that go red. One new problem blocks the merge: the V2 pool puts magpie-local-<hex> names into the prompt Kimi sends upstream. Two guards also need tests.

Blocking 1: the V2 pool sends "magpie" to the vendor in every request

  • The pool keys are the catalog aliases magpie-local-<24 hex> (catalog.mjs:14, required by assertModelPoolAliases, adapter.mjs:644). COMPATIBILITY.md itself says Kimi's V2 "generated tool description lists the actual aliases and their role descriptions".
  • The bridge rewrites only payload.model and messages (through applyInstructions). tools and earlier assistant tool calls go upstream unchanged (adapter.mjs:389-407). So every Work request sends the Agent tool's schema and description naming magpie-local-…, and every Agent call in the history repeats model: "magpie-local-…". Those requests go to whichever vendor Magpie routes to (Codex/OpenAI in the PR's own acceptance run).
  • This package already fixed the same class once: #356 / a5c13b8, guarded by no-magpie.test.mjs. The repo rule (LESSONS: "Text magpie adds to a prompt that goes to a vendor names neither magpie nor the vendor's limits") treats this as ban risk. The role descriptions ("Primary model / 主模型") are neutral and fine.
  • Before this commit the legacy path used the literal primary/secondary, so no alias reached the prompt. This exposure is new.
  • Fix: give the pool neutral keys. One way is a neutral alias prefix for new catalog entries, with isMenuAlias still matching the old magpie-local- form so cleanup and migration keep working. The other is to rewrite the alias strings in tools and tool-call arguments in the bridge, both ways. Then add a no-magpie test: a sample V2 request (Agent tool description and schema from the extracted 3.2.25 core, plus a history with an Agent call) leaves the bridge with no /magpie/i. If the menu displayName (… · Magpie) can also reach the description, cover that too.

Blocking 2: two V2 guards pass the suite when broken

Break (one at a time, reverted) Red
onExit no longer calls restoreModelPoolOnExit() (adapter.mjs:1200) 0
assertModelPoolAliases disabled (aliases not checked against the converted file's models/provider) 0
  • The synchronous host exit is what runs when Magpie quits. Without a test, nothing shows that a quit puts the user's own secondary_model.models/default_model back. Please add a V2 test that applies a pool, calls the sync exit path, and expects the baseline back, ownership kept and no RPC. The legacy path has one already.
  • Please add a test where the converted file has no model entry for the selected alias, or one with another provider, and expect secondary_model_unsupported_runtime with the file unchanged.

Not blocking (fix or say why not)

  • With the "reassert on restored baseline" clause removed (adapter.mjs:776) and the native default_effort capability guard turned off (adapter.mjs:765), the suite stays green too. A test for each would be good. The loop_control fingerprint check (adapter.mjs:201) also has no test. It is a layout heuristic, so that is fine.
  • "User edits Kimi's config, then sync": an external pool or default survives regeneration and disconnect, and is not called applied. Two tests go red when broken. But if the user then picks a different selection in Magpie, reassert writes over the external pool, and disconnect restores the pre-Magpie baseline, not the user's later edit. Please say so in the README, or keep an external snapshot as the restore target.
  • restoreModelPoolOnExit reads the converted file without a catch. In stop() every error there, kimi_config_changed and ENOENT included, becomes secondary_config_unsupported. Keep the real code when it is in SECONDARY_CODES, as the legacy branch does.
  • A half-written file: our own writes are temp+rename with a byte check. A torn read of Daimon's file that still parses (a truncated tail with no [secondary_model]) would be taken as a baseline with no pool. Disconnect would then remove the user's pool. This is only a risk if Daimon writes in place, and that is unknown. Say so in COMPATIBILITY.md, or re-read until two reads agree before capturing a baseline.
  • COMPATIBILITY.md says "Credential aggregates changed during the primary-selection interval; the cause was not isolated." Worth one line on what was ruled out (for example Kimi's own token refresh). It is not caused by this code as far as I can read: no credential is written.

Earlier items

  • r2 blocker (stop leaves alias and menu): fixed. Breaking it turns 2 tests red (legacy and V2 stop).
  • r2 minor (no test for settings_partially_saved after a secondary failure): fixed. Breaking it turns 1 test red ("a two-model POST reports partial persistence when only the secondary file save fails").
  • The r1 and r2 items checked in r2 are unchanged outside stop() and the V2 branches.

Hand breaks of the new V2 code (mote, Bun 1.4.2, HOME=$(mktemp -d), reverted after each)

Break Red
stop → restore({reload:false}) 2
pool exit restore always to baseline 2 (external pool / default kept)
observeModelPool never sees a foreign pool 1
an empty V2 batch counts as confirmed 2
reload always reports confirmed 2
pool baseline error rethrown 3 (primary still routes)
"untouched when nothing owned" guard removed 1
stop() pool restore removed 4
sync-exit pool restore removed 0
assertModelPoolAliases no-op 0
patch no-op text preservation removed 1
reassert on restored baseline removed 0
default_effort guard off 0
confirmedModelPool samePool check off 1
runtime/generation switch keeps pending ids 1
V2 byte check before commit off 1
loop_control fingerprint check removed 0
page primaryOnlyApplied branch off 2 (en + zh)
partial-save conversion off 1

Read

  • Every line of 30616f7's non-test files (adapter.mjs, model-pool-config.mjs, settings.html, package.json, README, COMPATIBILITY). The new tests use mkdtemp dirs, injected fetchImpl and rpcCall, and synthetic credentials. They make no real network calls and write nothing under HOME. No new process, URL or credential read. State keeps only aliases, descriptions, the default and ownership flags (plainPoolSnapshot).
  • The files list in package.json gains model-pool-config.mjs. registry.json summary updated.

Version, merge, CI

  • main has kimi-desktop 0.1.5. dist index.json has 0.1.0 to 0.1.5, latest 0.1.5. npm returns E404, so the package is not on npm. #813 is the only open kimi-desktop PR, so 0.1.6 is the next free version. OK.
  • Merges cleanly into origin/main e7cb9dc (only registry.json auto-merged). The merge-base 1975c93 is old, but main has no kimi-desktop changes since then.
  • PR CI at 30616f7: run "pr" completed success (on-npm pass, test pass 3m46s). Not action_required.

Run (merged tree a482af2c = origin/main e7cb9dc + PR head, on mote, Bun 1.4.2, HOME=$(mktemp -d))

  • bun test packages/kimi-desktop: 165 pass, 0 fail, 1303 expects, 12 files.
  • bun scripts/check.mjs kimi-desktop: ✓ registry.json, exit 0. bun scripts/check.mjs: exit 0, no ✗.
  • Repo bun test: 6804 pass, 20 skip, 0 fail, 489 files.
  • Logs: ~/workspace/projects/magpie-wt/pr813r3-scratch/{run1.log,breaks.log}.

Not tried

  • A real Kimi Desktop (none on the test machines). The upstream "magpie" exposure comes from reading the code and the PR's COMPATIBILITY.md, not from a captured request.
  • Windows and Linux. WSL was not run this round, since the only change is in the package and r2's WSL run was green.
  • The page inside Magpie's GUI.

中文总结

r2 的阻塞点已修复:stop() 重新走正常 restore,恢复 Kimi 原生模型和菜单,改回 reload:false 后 legacy 与 V2 两个测试变红。V2 模型池的实现很谨慎:只写 secondary_model.models 和 default_model,写入前比对字节、temp+rename,保留外部写入者的池,从未选副模型的用户完全不碰。大部分守卫手动破坏后都有测试变红。mote 上包测试 165 通过,两种 check.mjs 通过,根 bun test 6804 通过、0 失败。PR CI 绿,版本 0.1.6 可用,可干净合并。
仍需修改:V2 池的键是 magpie-local- 别名,会随 Agent 工具描述和工具调用参数原样发给上游厂商,与 #356 已修过的问题同类(工具描述里出现 magpie),需改用中性别名或在 bridge 中改写,并补 no-magpie 测试。另外,同步退出时恢复 V2 池、以及别名校验,这两个守卫去掉后测试仍全绿,需补测试。结论:request changes。

@lilongsky
lilongsky marked this pull request as draft November 7, 2026 06:41
@lilongsky lilongsky changed the title kimi-desktop: configure primary and secondary models in Work feat(kimi-desktop): configure Work primary and secondary models Nov 7, 2026
@yetone

yetone commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Round 4 at cc9a023: the round 3 blockers are fixed. Nothing upstream says magpie any more, and a test goes red when the alias rewrite is removed. The pool question no longer applies.

What was run on macOS (Bun 1.4.2, temporary HOME), with the PR merged onto main a313a00:

  • package tests: 234 pass;
  • check.mjs: passes;
  • repo-wide bun test: 6983 pass, 0 fail.

Three guards still go untested. Removing each one by hand leaves the suite at 0 fail:

  1. adapter.mjs:424: while the companion plugin isn't ready, the original tools go back and the Agent call isn't rewritten. Please add a bridge test with rolesReady=false.
  2. adapter.mjs:552, catalog_unavailable when the native list is empty. Without it, a reply holding only local-model-* rows empties the user's Kimi model menu. Please add a test: no update or setModel RPC, and catalogError is set.
  3. adapter.mjs:663: wait while the runner is busy before installing or updating the plugin. Please add a test with a busy runner: no kimiPlugin.* RPC, and {waiting:true}.

On the 0.1.5 → 0.1.6 behaviour change

  • Taking magpie's models out of Kimi's menu is fine.
  • The companion plugin should not outlive the connection. Disconnect should leave the user's Kimi as it found it, so please uninstall local-work-model-roles-* on disconnect, with a test, and update README:47.

Minor: agent-routing.mjs:85's match[2] === 'd' can be dropped with nothing going red. One assert would cover it.

Not tried: a real Kimi 3.2.25 with the companion plugin, and an upgrade from an installed 0.1.5.

longsky li added 2 commits November 8, 2026 00:30
…condary-models-20261107

# Conflicts:
#	registry.json
Persist opaque bindings before profile publication, preserve native Agent profiles,
and recover primary state while disabling, reloading and removing owned companions.
Cover streaming continuity, control throttling, write conflicts and settings status
with isolated regression tests and verified assertion-failing guard mutations.

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

Round 5 at 6e3257e: approved. All five round-4 asks are done.

  1. A bridge test with rolesReady=false (review-lifecycle.test.mjs): payload passes through, no router.
  2. catalog_unavailable with an empty native list (catalog-migration.test.mjs): no update/setModel RPC, catalogError set.
  3. Busy runner (review-lifecycle.test.mjs): no kimiPlugin.* RPC while busy, {waiting:true}.
  4. local-work-model-roles-* is retired on disconnect, clear and stop (disable, reload, provenance recheck, removePlugin), and README covers it.
  5. match[2] === 'd' is asserted (agent-routing.test.mjs).

What was run, on a separate machine with a temp HOME, at a merge of 6e3257e onto main 31080df:

  • bun test in packages/kimi-desktop: 384 pass, 0 fail.
  • bun scripts/check.mjs kimi-desktop: exit 0.
  • Repo-root bun test: 7210 pass, 20 skip, 0 fail across 517 files.

Each new guard was broken by hand and went red:

Mutation Tests failing
roles bridge ignores rolesReady 2
empty native list accepted 1
busy check removed 2
no removePlugin on retirement 12
no disable before removal 16
no retirement on a disconnect tick 20
no retirement in stop() 4
match[2] === 'd' dropped 1

Follow-up, not blocking: #850. adapter.mjs:472 returns 409 agent_profile_unbound for any body quoting [agent-profile-route, even on a connection that never turned roles on, and the history keeps it there. Probe: 409, nothing forwarded.

Minor: role-plugin-state.mjs carries twenty // MUTATION … notes. They're harmless; drop them whenever convenient.

Not tried: a real Kimi desktop 3.2.25 with the companion plugin installed and removed through its own RPCs, an upgrade from an installed 0.1.5, and Windows or Linux.

@yetone

yetone commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Approved at 6e3257e (review above). The PR is still a draft, so it can't merge yet. If 6e3257e is the version you want merged, mark it ready for review and it will be squash-merged at that commit. A new push would need another look first.

@lilongsky
lilongsky marked this pull request as ready for review November 8, 2026 16:01
@lilongsky
lilongsky marked this pull request as draft November 8, 2026 19:06

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

Round 6 at f07e7f4: approved. I read every line of 6e3257e..f07e7f4 (19 files).

The changes hold up:

  • Shared instructions are now applied before the router runs. With a routing key, route markers in them are neutralized, so neither the old block nor its replacement can bind a model.
  • Catalog absence is counted only after a successful sync. A binding is pruned after 3 absences over at least 10 minutes, and only when the runner is idle and the owned profile files match. The count survives a restart through state.
  • The settings server is retried on each adapter tick. A port change made while a bind is pending wins, and stop() waits for a pending start.
  • The page retries three times and then shows a Retry button.
  • Hidden files in agents/ are skipped.

Run on a separate macOS box, with a sandbox HOME and TMPDIR:

  • packages/kimi-desktop: bun test passed 464 of 464.
  • bun scripts/check.mjs kimi-desktop: ✓

I broke each change by hand, and each one goes red:

  • neutralizeText returning its input unchanged, together with pruning on the first absence: 36 tests fail. These include the R2-1 neutralize tests, the R2-2 grace and recovery tests, and the P2 retirement preflight tests.
  • Dropping syncSettings from the tick and the port-change loop in index.mjs: 3 tests fail (tick recovery, newer port wins, one warning).

One nit, not blocking: removing the // MUTATION ... comments in role-plugin-state.mjs left about 13 blank lines inside those functions. They can be cleaned up in a later change.

No telemetry was added, and no added text names magpie upstream.

@lilongsky lilongsky changed the title feat(kimi-desktop): configure Work primary and secondary models feat(kimi-desktop): add independent secondary models and inference settings Nov 8, 2026
@lilongsky
lilongsky marked this pull request as ready for review November 9, 2026 01:37
@yetone
yetone merged commit bc5b833 into magpie-community:main Nov 9, 2026
2 checks passed
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.

2 participants