build: migrate the package manager from pnpm to Bun - #4
Merged
Conversation
added 30 commits
August 23, 2026 21:59
TypeScript 6 and tsgolint treat "baseUrl" as an error-level deprecation. Resolve #/* subpath imports through per-package paths mappings instead, which also makes the source-first workspace resolvable under Bun 1.4.
Detect the runtime at spawn time: under Bun use the built-in Bun.Terminal PTY (decoding chunks to strings and taking the real subprocess exit code from the exited promise), elsewhere keep the existing node-pty path. The TerminalProcess contract is unchanged.
Mechanical fixes only (import dedup, catch parameter naming, redundant type conversions), audited for semantics; agent-core-v2 excluded because its module-registration side effects depend on import statement order. Structured log keys and wire payload keys renamed by catch-error-name are restored explicitly.
Add build:native:bun alongside the existing SEA pipeline: reuse the tsdown bundles and asset collectors, stage all embedded files with a .bin suffix (bun compile otherwise parses .cjs/.json imports as modules), generate a key-to-path manifest module, and compile with bun build --compile. At runtime a Bun-backed NativeAssetSource feeds the same extraction and validation path used for SEA, so workers, web assets, and native bindings behave identically. Also migrate pnpm overrides from package.json (ignored since pnpm 10.33) into pnpm-workspace.yaml to keep the hardened dependency floor active.
Add a build-bun workflow input (default false) that builds, smokes, and packages the Bun single-file binary on ubuntu-24.04. The build script now maps KIMI_CODE_BUILD_TARGET to bun compile targets and reuses the shared sign/checksum step for artifact parity.
Convert || to ?? only at sites whose declared types exclude empty strings, zero, and false; wire-parsed and index-access sites stay on || deliberately.
Replace non-null assertions with explicit guards where values cross trust boundaries: RESP command arity from clients, worker postMessage results, disk-ref reads through optional handles, and empty-token caller input. Hot-path skiplointer chasing keeps its structural invariants documented by loop bounds.
Union narrowing over real payload types for TUI session replay, Event2<unknown> for event bus seams, contravariance-correct constructor params in the channel registry, and typed React global lookup in i18n-shared. The public SDK SessionMeta.custom record stays as-is (consumer-visible contract).
The failure path deactivated the agent context before disposing the scope, so the activity view's final dispose-time publish hit the event bus with no active lifecycle context and crashed teardown — masking whatever caused the failure. Dispose first, then deactivate, matching the order the normal close path already uses. Verified end to end: a headless prompt against a mock OpenAI-compatible endpoint now reports its actual configuration error instead of crashing, and completes cleanly under both Node and Bun once configured.
Config errors during agent creation must surface their real semantics (a missing max_context_size) rather than being masked. The lifecycle ordering fix is verified manually against the CLI spawn path; this suite locks the SDK-level contract.
Replace non-null assertions with the package's existing validation and error conventions: kosong.addProvider validates through the facade's zod schema, and memory-transport stream dispatch raises REQUEST_INVALID when a source fails to start. A node-sdk lazy-init switches to ??=.
Narrow ServiceIdentifier maps to unknown, type Promisify/Promisable via Promise<unknown>, give provideUnit's overload a precise second-parameter type, and lift implementation notes from inline comments onto the exported symbols they document (kosong error classifiers, FetchURLTool, event dispatcher folds).
Bun.Terminal delivers raw bytes, so decode them with a streaming TextDecoder instead of assuming chunk-aligned UTF-8; flush the decoder before the exit event so a trailing truncated sequence surfaces as U+FFFD like node-pty would report it, and stop emitting empty strings for chunks that only carry part of a multi-byte character.
Describe resume_agent_ids as the flat agent_id-to-prompt record it is, warn against arrays and {{item}} keys, and refresh the toolset hash snapshots plus pinned token counts that the longer description shifts.
…dings Bun ignores Module._load overrides and routes .node specifiers straight to dlopen, so materialize pi-tui's platform helper next to the extracted entry where its own candidate search looks, and do the same for the whole node-pty binding tree. On Node SEA the module hook now also redirects node-pty's relative requires into the native cache. Register node-pty in the native-dep registry, preserve POSIX file modes through asset extraction so darwin's spawn-helper stays executable, and dlopen the binding in the release smoke on every target.
Detect packaged Bun installs via the embedded-asset marker (Bun lacks node:sea) and report {native,kind} instead of a boolean. The release manifest gains an optional bun section; a Bun packaged client stages from it and refuses to update when the release ships no Bun build, rather than silently swapping itself to the Node SEA binary. Staged metadata records the engine.
Default LocalFetchURLProvider to the vendored undici fetch instead of globalThis.fetch: the pinned-DNS dispatcher option only exists in undici, and on runtimes whose global fetch is not undici (Bun) it would be ignored — silently dropping DNS pinning and re-resolving through whatever resolver the runtime prefers.
Render a Runtime row (e.g. 'bun · rust') from the packaging detection and the native-tools probe, wiring up the previously dead nativeTools option so rollout builds are identifiable at a glance.
The content-addressed extraction dir accumulates one main.cjs per upgraded version; sweep entries untouched for over a day at startup (young files may belong to a concurrently starting older binary). Add scripts/native/bench-native.mjs to compare cold-ish start wall time across packaged builds.
Run the Bun pipeline through the same six-target matrix as SEA with release-profile parity (built-in catalog, APPLE_SIGNING_IDENTITY, codesign self-check), package it under engine-suffixed artifact names, aggregate bun checksums into the manifest's optional bun section (and refuse a platforms-less manifest), and enable build-bun on releases and manual bundles. Bytecode is on by default with KIMI_CODE_BUN_NO_BYTECODE as the escape hatch.
The loader builds its candidate paths by string concatenation, so separator runs arrive doubled (prebuilds/linux-x64//pty.node) and the redirect pattern never matched — caught by the new PTY-binding smoke on a real linux-x64 SEA build. Assert the binding marker explicitly in the smoke output.
…sessions Bundled copies of node-pty cannot work in single-file builds: their binding requires resolve against the bundle file, which exists at neither the SEA nor the Bun runtime layout — and Module._load overrides are never consulted inside the SEA runtime, so loader-hook redirects are dead code there. Load through the injected asset-cache resolver instead, addressing the package entry file directly because Bun does not implement self-reference resolution from a package.json path.
Packaged single-file builds inject __kimi_getNativePackageRoot; consult it before the on-disk candidate paths so darwin/win32 helpers load in SEA and Bun builds, where neither moduleDir nor execPath candidates contain the helper.
Keep node-pty external to the native bundle (like kimi-native-tools), ship its full package with per-target bindings through the asset pipeline, and resolve it at the call sites via a shared loader that prefers the extracted cache — verified end-to-end by the PTY-binding smoke on real SEA and Bun linux-x64 builds. Remove installNativeModuleHook entirely: the SEA runtime never consults Module._load for bundle-internal requires, so its redirects never fired; drop the Bun materializers that depended on the same premise.
Trigger the manual native bundle workflow on pushes to feat/bun-migration (docs-only changes excluded) so the six-platform Bun matrix is verified automatically. Keep workflow_dispatch. Gracefully skip macOS code signing when the APPLE_* secrets are not configured (e.g. on this fork): relay secret presence through a job-level env (the secrets context is unavailable in step-level if expressions) and fall back to the local-profile build instead of failing in keychain setup.
…pi linking The asset collector requires a prebuilt kimi-agent .node next to the package, but no CI step ever built one — local builds only passed on machines with a leftover untracked artifact. Add the build step to both matrix jobs after kimi-native-tools. macOS cdylib linking fails with undefined _napi_* symbols because the fork's custom build.rs does not emit dynamic_lookup link args; add the standard apple-target rustflags to both Rust addons' cargo config.
Compiled Bun executables would autoload .env / bunfig.toml from the user's cwd at runtime, silently diverging from Node/SEA behavior — disable both via the compile flags (verified against bun 1.4.0). Bytecode becomes opt-in via KIMI_CODE_BUN_ENABLE_BYTECODE=1: measured no startup gain on this pipeline (only the entry shim is bytecode'd; main.cjs stays outside the graph) while adding size and locking the artifact to the exact Bun build version. Correct the stale top-level await note in bun-entry.ts: the CJS default output format is what constrains TLA, ESM output has supported it since Bun 1.3.9.
added 11 commits
August 24, 2026 16:53
The two-line pnpm-workspace.yaml in apps/kimi-code hijacked workspace resolution under pnpm >= 11, breaking 'pnpm --filter @moonshot-ai/kimi-code run test' from every location. The override and its self-link dependency were dead code (dropped from the lockfile importer long ago); removing the file restores the documented test entry from both the repo root and the app directory.
Fold the experimental native-bun section into a broader Bun migration section covering the runtime integration work (node-pty/pi-tui asset cache loading, dual-runtime terminal sessions, engine-aware self update, cross-runtime fetch semantics), state verified-platform status honestly, and lay out the roadmap: full CI matrix validation -> bytecode/TLA constraint cleanup -> default engine switch and SEA retirement.
The smoke test requires <binDir>/native/<os>/prebuilds/<arch>/<helper>.node, and the runtime loader keeps it as a candidate path, but neither the SEA nor the Bun pipeline ever produced that layout after the bun materializer was removed — collection recorded the helper in the manifest while nothing copied it next to the binary, so every darwin/win32 job failed at smoke with a missing-module error (linux ships no helper and passed). Add an exec-side staging step derived from the collected manifest: map pi-tui's native/**.node entries onto the executable-side relative layout and copy them into place for both pipelines. 02-sea-blob now returns the collection result so build.mjs reuses it instead of collecting twice.
Two related signing gaps: bun --compile may emit an already-signed binary on darwin-x64, making the plain 'codesign --sign -' step fail with 'is already signed'; and SEA blobs that skip the numbered-script chain were never re-signed after postject injection, leaving a stale signature that macOS kernels reject with a silent early kill. Pass --force on adhoc signing and unconditionally adhoc re-sign darwin binaries right after NODE_SEA_BLOB injection; release-profile identity signing runs later and overrides it.
Node's darwin SEA runtime resolves the injected blob via
getsectdata("NODE_SEA", "NODE_SEA_BLOB"), and postject defaults to a
__POSTJECT segment name when none is given — the resulting Mach-O dies
silently right after exec, which is exactly how the darwin local-profile
SEA jobs failed while linux/win32 (and the whole Bun track) passed.
Upstream avoids this by passing --macho-segment-name NODE_SEA to the
postject CLI; pass machoSegmentName through the API instead.
- Remove the standalone ReadMediaFile tool; Read handles media directly (mediaReadContext, execute-media-read), with legacy-name aliasing kept for permission rules and session replays - Bundle the runtime into the Bun compile entry graph: drop the /tmp extraction + dynamic import startup path; add bun-assets.setup.ts - produce-manifest.mjs now fails when the manifest's bun section is missing any supported target; _native-build.yml gains a build-sea input - Update CONTRIBUTING (en/zh) bytecode wording to match the new pipeline - Fix stale test expectations (image placeholder copy, mcp test harness)
- bunfig.toml pins the hoisted linker; bun.lock generated and committed - Full overrides table migrated from pnpm-workspace.yaml into package.json so both package managers resolve identical versions - trustedDependencies allowlist replaces pnpm's onlyBuiltDependencies; node-gyp added as a devDependency for node-pty's install script - ssh2 optionalDependencies (cpu-features, nan) removed via patches/ssh2@1.17.0.patch, preserving the supply-chain hardening that previously relied on pnpm's '-' override syntax - tree-sitter-bash differential harness resolves the reference wasm via createRequire instead of a pnpm-layout path - Fix pre-existing type error in executeMediaRead header parameter
- en/zh docs: ReadMediaFile references updated to the merged Read tool - Add read-folds-in-media changeset for the upcoming release - Reformat touched permission/read modules with oxfmt
- Root scripts and Makefile: pnpm -r/--filter/-C replaced with bun run --workspaces/--filter and cd chains; packageManager field removed - All release workflows (ci, release, _native-build, pkg-pr-new, docs-deploy) install with bun install --frozen-lockfile via oven-sh/setup-bun@v2 pinned to 1.4.0 plus a bun.lock-keyed cache; the typecheck job runs tsgo through bunx - Delete pnpm-workspace.yaml, pnpm-lock.yaml, and .npmrc; Bun is now the only supported package manager
…oling - Root AGENTS.md, CONTRIBUTING (en/zh), READMEs, package AGENTS/READMEs, .changeset/README, and agent skills now instruct Bun equivalents - check-nix-workspace.mjs reads workspaces from root package.json (pnpm-workspace.yaml is gone) and validates workspacePaths only; flake.nix no longer carries a workspaceNames list - Generated manifest headers and their generators updated in lockstep - Functional script fixes: node-sdk build:dts, vis dev concurrently commands, build-vis-asset vite invocation, vscode packaging error hints, native asset install hints
|
❌ Nix build failed Hash mismatch in
Please update |
3 tasks
7723qqq
added a commit
that referenced
this pull request
Aug 31, 2026
…ne paths P38 G-6 item #4: the napi and stdio paths built NativeToolCallbacks with plan_guard: None, so plan-mode Write/Edit interception only existed in the REPL — the permission policy chain has no plan concept and the host's check_permission cannot cover it. The guard now reads the host's plan state through the existing state bridge (host/state_read domain=plan) before every guarded native call — zero wire changes, and mid-turn EnterPlanMode/ExitPlanMode are naturally covered (a per-turn snapshot would go stale there). PlanGuard becomes async; the guard denial now emits the tool.native event like the permission denial does, so the transcript records the card's terminal state. The decision lives in tools::plan_mode::plan_denial (v2 AgentPlanService.guardToolExecution semantics: Write/Edit restricted to the plan file, TaskStop/CronCreate/CronDelete denied, component-wise path comparison for mixed separators). Unguarded tools exit before any state round-trip; a failed state read fails open with the permission layer still in charge. The shared function also fixes a REPL gap: CronCreate/CronDelete were not denied there before. Verified: cargo test --lib 824 green (8 new plan_denial unit tests, REPL cron cases), stdio integration 10/10 including a new end-to-end case (plan active + native Write outside the plan file -> guard vetoes before the permission round-trip, exactly one plan state read, tool.native event carries the denial, nothing lands on disk), clippy 0 warnings, fmt clean.
7723qqq
added a commit
that referenced
this pull request
Sep 1, 2026
v2 goalAgentRuntime 的 CreateGoal 启动审批与陈旧 goal 拒绝此前对原生 执行完全失效(goal 静默启动、旧轮可改已变更的 goal)。本批在引擎内补齐: - tools/goal_guard.rs(新增):GoalGuard——turn 起始 goal 绑定表 + requires_host(非 auto 路由)+ stale_denial(突变工具双拼写、 goalId 比较、goal 清空即 stale、读失败 fail-open、文案逐字节对齐 v2)。 - #7 审批 = 路由回宿主:非 auto 模式(含 mode 未知 fail-closed)下 CreateGoal 不经原生执行,走宿主 executeTool——goal-start 审批链 (含 mode 切换面板)原样生效,零重实现。mode 取 pipeline 快照 (PermissionEngine::mode())。 - #8 stale veto:run_turn 入口经新 HostCallbacks::set_turn_goal 绑定 turn→goal(默认 no-op,NativeToolCallbacks/SteerQueueCallbacks 转发, 零装配点改动);gate 在 permission 后插入 stale_denial,denial 发 tool.native is_error + 合成结果,不回退宿主。 - 预算宽限轮由 run_turn 硬停结构性覆盖,无需复刻(文档说明差异)。 - 顺带修复 napi callbacks.goal() 死缝:NapiHostCallbacks 增 goal_fn 并实现(session 接线,legacy 留 None fail-open)。 - 验证:cargo lib 866(goal_guard 6 + 门级 4 + 绑定 2)、stdio 16/16 (CreateGoal 无快照必回退 E2E)、napi-integration 49/49(manual 回退/ auto 原生/session stale E2E)、clippy 0、oxlint 0 errors。 - ROADMAP P38 #7/#8 销账 + P42 文档(含诚实边界:mode 会话级陈旧、 REPL 无审批、预算硬停差异);changeset 记录用户可见行为。 迁移队列:#4 P39 ✅、#3 P41 ✅、#7+#8 P42 ✅ → 剩余 #6/#2/MoonshotAI#13。
7723qqq
added a commit
that referenced
this pull request
Sep 1, 2026
v2 agentExternalHooksService 的用户 PreToolUse 钩子此前对引擎原生路径 零对应(原生工具执行不触发)。本批在引擎内全量执行: - tools/external_hooks.rs(新增):HookGuard——event 过滤(只 PreToolUse)/matcher 正则(非法跳过)/command 去重/并行(tokio join_all,按序取首个 block);平台 shell spawn;stdin 写 snake_case 载荷(hook_event_name/session_id/cwd/client_type/session_title/ tool_name/tool_input/tool_call_id);超时 select + kill;三分支判定 (exit 2 → stderr;stdout JSON deny → reason;其余 allow)与 fail-closed 文案(failed to spawn / timed out / errored)逐字节对齐 v2。 - 配置随 PolicySnapshot 推送(零新 wire 字段):PolicySnapshot 增 pre_tool_hooks;宿主 rust-engine.ts getPolicySnapshot 从 loadRuntimeConfigSafe 读 [hooks];REPL 经 KimiConfig.hooks 段 + build_policy_snapshot。 - gate 集成:permission allow 后、goal_guard 前(镜像 v2 链序); denial 发 tool.native is_error + 合成结果,不回退宿主。 - 验证:cargo lib 879(external_hooks 11 + 门级 2)、stdio 18/18 (exit2 拦/exit0 放行 E2E)、napi-integration 51/51(真实 .node)、 rust-engine 25/25(宿主推送零回归)、clippy 0、oxlint 0 errors。 - ROADMAP P38 #6 销账 + P43 文档(含诚实边界:载荷字段近似、kill 链 降级、快照会话级推送、其他 19 种事件仍归宿主、cmd 引号教训); changeset 记录用户可见行为。 迁移队列:#4 P39 ✅、#3 P41 ✅、#7+#8 P42 ✅、#6 P43 ✅ → 剩余 #2(toolDedupe)、MoonshotAI#13(tower worker,随 M3)。
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.
Related Issue
无关联 issue——本仓库(fork)的基础设施改造,源自会话内的迁移评估与批准计划(pnpm → Bun 包管理器切换,独立于进行中的发布引擎 SEA→Bun 迁移
feat/bun-migration)。Problem
仓库此前同时依赖两套工具链:发布产物已由 Bun 编译(
build-bun.mjs),但开发/CI/Nix 的包管理仍是 pnpm。评估实验(冷装 4.2s vs 分钟级、热装 0.42s)确认收益集中在安装与开发迭代速度,且四个必改项均有明确解法,故按批准的两阶段计划完成整体切换。What changed
阶段一(共存期)
bunfig.toml(钉死 hoisted linker)与bun.locktrustedDependencies替代onlyBuiltDependencies;node-gyp 显式 devDep(Bun 不自供)patches/ssh2@1.17.0.patch剔除——pnpm 的-移除语法在纯 Bun 下不生效,此补丁保住原有供应链加固语义阶段二(切换)
packageManager字段oven-sh/setup-bun@v2(钉 1.4.0)+ bun.lock 键缓存 +bun install --frozen-lockfile验证
bun install --frozen-lockfile:1680 包 3.6s;全量 vitest 16176 用例通过(1 例负载性 flake 单跑复现即绿);typecheck/build 全绿bun run build:native:bun+ 冒烟(版本一致性/native asset/workers/PTY binding)通过bunDepsFOD hash 为占位值,首次 CI 运行会以 got-hash 失败并由 nix-build bot 评论真实哈希,届时回填即可Checklist
gen-changesetsskill — decision: no changeset, the package-manager switch is not user-perceivable in the shipped artifact.