feat(kimi): native Kimi Code CLI dialect support (Fixes #1561) - #1564
Conversation
Same pattern as the chenhg5#1456 / PR chenhg5#1461 --print fix. Newer Kimi Code CLI builds no longer accept --work-dir, exiting with `error: unknown option --work-dir` whenever the user's config sets a non-default workspace directory. - agent/kimi/probe.go: kimiFlagSupport gains a WorkDir bool; the probe fills it from `kimi --help` (parseKimiHelpFlags already scans the --work-dir token, no parser changes needed). - agent/kimi/session.go buildArgs: --work-dir is now emitted only when flagSupport.WorkDir is true. The agent still runs in the correct directory via exec.Command.Dir (set separately), so omitting the flag on modern CLIs is functionally equivalent for users on default dirs and graceful for users on non-default dirs whose CLI just ignores it. - agent/kimi/probe_test.go: extend the legacy test to assert --work-dir is detected, add modernKimiHelpWithoutWorkDir constant that mirrors the build the reporter hits, plus full TestKimiFlagSupport_LegacyHelpSetsWorkDir / TestKimiFlagSupport_ModernWithoutWorkDir coverage of the probe mapping. - agent/kimi/session_test.go: TestBuildArgs_WorkDirFlagGated (no flag on modern CLI) + TestBuildArgs_WorkDirFlagEmitted (legacy CLI keeps it). Also asserts the work-dir value doesn't leak into args when the gate is closed, catching a partial-gate future bug. go test ./agent/kimi/... — all pass.
Fixes chenhg5#1561 The kimi agent targeted the legacy Python kimi-cli dialect; the newer Node.js Kimi Code CLI (kimi-code) speaks a different one. Extend the chenhg5#1461/chenhg5#1483 probe-gating approach to cover the remaining differences: - probe: also detect --quiet; add isModernFlavor() using --print absence as the family discriminator established in chenhg5#1456 - buildArgs: resume with -r instead of --resume on the modern dialect (--resume is rejected; -r matches the CLI's own resume hint); gate --quiet and emulate quiet mode via local event suppression when the binary dropped it; never pass --yolo/--auto (bare --prompt already auto-approves, and Kimi Code rejects combining them) - stream-json: accept plain-string content for assistant/tool messages alongside the legacy block-array shape (format-tolerant, no gating) - session continuity: capture the session id from Kimi Code's stdout meta line {"role":"meta","type":"session.resume_hint"} instead of the legacy plain-text/stderr hint - session listing: scan both ~/.kimi/sessions and ~/.kimi-code/sessions, understand the Kimi Code state.json schema, honor its workDir field Tests: real v0.26.0 --help fixture, per-dialect arg tests, content-shape and meta-hint regression tests, dual-flavor session listing test, and an env-guarded live e2e (KIMI_LIVE_E2E=1) that verifies anchor recall across a resumed turn against the production binary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kimi Code CLI sessions have no context.jsonl; their transcript lives at agents/main/wire.jsonl. Without a fallback, /list showed 0 messages for every modern session. Count user turns from wire.jsonl (and use the first user turn as the summary), matching legacy kimi-cli behavior. Addresses review feedback on chenhg5#1564.
CI lint failed on the PR merge ref: main added a messageID string param to
AgentSession.Send (core/interfaces.go), but the PR still called the old 3-arg
form in live_e2e_test.go, so the merged code failed to compile
('not enough arguments in call to s.Send').
origin/main was merged into kimi-code-flavor (no conflicts); kimi's Send now
carries the 4-arg signature matching main. This updates the remaining 3-arg
call site to the new signature. agent/kimi and core compile, vet and tests green.
golangci-lint flagged the deferred file Close calls in the new parseKimiTranscript/count* helpers as unchecked errors. Use an explicit blank-assignment close to satisfy errcheck. agent/kimi still compiles, vets and tests green.
|
@chenhg5 — quick status update after the CI failure:
Nothing is outstanding from my side. Ready for the QA pass / review whenever convenient — happy to iterate on any feedback. Thanks! |
chenhg5
left a comment
There was a problem hiding this comment.
Conclusion: Comment
Overall assessment:
Solid technical work — the dialect detection, flag translation, content-shape tolerance, resume-hint capture, and quiet-mode emulation are all correct and well-tested. Two process concerns need a maintainer call before merge: (1) this branch is stacked on the still-open PR #1483, and (2) there are three other open PRs also targeting #1561 that should be reconciled with this one.
Review scope:
- Read full diff across 8 files (884 / 121).
- Ran
go test -race ./agent/kimi/... -count=1 -timeout 90s(race detector required) — 27 tests PASS, including all new ones, legacy block-array parser tests, and probe parser tests.go vet ./agent/kimi/...clean. - Cross-checked against competing PRs #1566, #1586, #1601 (all targeting #1561).
- Verified backward compatibility on every dialect branch.
✅ What looks good:
- Probe-driven branching is the right architecture. Probe failure defaults to modern (safe, matches
#1461's direction). - Verbatim
kimiCodeHelpV026fixture inprobe_test.goanchors the family discriminator to the real production binary — much stronger than synthetic help text. - Format-tolerant parser (plain-string → block-array) keeps both flavors working without explicit flavor checks at every site.
kimiSessionsBaseDirscleanly extends session listing without changing legacy behavior. Workdir filter only applies whenstate.WorkDir != "", preserving legacy list-all semantics.parseKimiTranscript's "only fall back to wire.jsonl when context.jsonl msgCount == 0" is the right call.countWireJSONLcorrectly filters bytype == "context.append_message" && origin.kind == "user"to avoid double-counting tool events and streamed assistant chunks.handleMeta's fallback to extracting the id from the legacycontentfield is a nice belt-and-suspenders move for forward compatibility.- Race detector clean.
🚨/🔴 Must fix:
None — no correctness, security, or data-loss bugs found.
🟠 Should improve:
- Stacked on PR #1483 which is still OPEN. Base of this branch is
e1ec734f, the--work-dirgate commit. Maintainer should either (a) merge #1483 first and ask author to rebase, or (b) merge this one and accept that--work-dirwork goes in via #1564. Coordinate before approving. - Multiple competing PRs target #1561.
#1566,#1586, and#1601are also open, with overlapping scope.#1601(highland0971, 142 LOC) is a much smaller alternative that covers the core dialect-compat surface without the session-listing and quiet-mode extras. This PR is more comprehensive and has the strongest evidence (live e2e against real v0.26 binary), but the maintainer should pick one. Recommend a maintainer call before merging any. - Unrelated
docs(sponsors)commit3fc360eeis bundled. Addstrack_idquery strings to Kimi platform links in README.md, README.zh-CN.md, and provider-presets.json. This is sponsor policy, not kimi-code dialect work — should be a separate PR for clean cherry-pick/bisect. parseKimiTranscriptsummary fallback edge case. Ifcontext.jsonlexists withmsgCount > 0but no user-text summary (e.g. user content was empty),wire.jsonlis not consulted and summary stays empty. Low practical risk, but worth a comment or test.
🔵 Optional:
- Plain-string
handleAssistantdoesn't extract thinking; if a tool_call follows, buffered text gets emitted asEventThinkingbyflushPendingAsThinking. Same pre-existing bug exists in the legacy block-array path, so not a regression — but a comment acknowledging the lack of a separate thinking channel would help future maintainers. isModernFlavor()could use an explicit test pinning a mid-migration case likekimiFlagSupport{Print: true, WorkDir: false}(workdir dropped before print) to lock in the discriminator intent.TestBuildArgs_WorkDirFlagGatedasserts"/"doesn't appear in args — only meaningful because the workdir happens to be root. Tighten to a sentinel.
❓ Questions:
- What is the merge plan with #1483? Will maintainer merge that first and ask author to rebase?
- Will maintainer pick #1564 or #1601 to land #1561? (Or pick differently — e.g. split this PR into core-dialect + session-listing?)
- The new state.json filter behavior: legacy sessions are still listed across all workdirs (preserved), but modern sessions are now workDir-scoped (new). Worth confirming this asymmetry is intentional.
Testing / Risk:
- Verified evidence:
go test -race ./agent/kimi/... -count=1 -timeout 90s— all 27 new tests pass, all pre-existing tests still pass.go vetclean.go build ./agent/kimi/...clean. (Theweb/embed.gobuild error is a pre-existing main-branch issue, not introduced here.) - Author reports
go test ./... green (Go 1.25.12). Other packages not affected:agent/kimionly importscoreand the diff doesn't touchcore. - Live e2e (
TestLiveE2E_KimiCodeFlavor) is env-gated and not in CI. Author reports it passes against~/.kimi-code/bin/kimiv0.26.0 with session resume. - Remaining risk: future kimi-code versions may add new event types or content shapes that this PR doesn't handle. Format-tolerant parser mitigates but doesn't eliminate this — recommend a follow-up test pinning the v0.29.1 (or current latest) verbatim wire output.
Next step:
Maintainer: pick a merge strategy for #1561 (merge #1483 → rebase #1564 → merge, or merge #1601 instead, or split this PR). Once decided, this PR is technically ready to land.
|
@whyihaveyou 你的 #1564 现在可以 rebase 了。 #1483 (commit e1ec734) 刚刚已合并进 main — 它原本就是 #1564 stack 的 base commit, 现在已经被官方收下了, 所以 rebase 到最新的 git fetch origin
git checkout kimi-code-flavor
git rebase origin/mainrebase 之后 #1564 的 diff 只会剩下:
(原来 stack 里的 e1ec734 会自动从 history 里消失, 因为它已经在 main 上了。) rebase 完成后 ping 我, 我重新 review 一遍就可以合并了。 — cc-connect/qa-claudecode |
Fixes chenhg5#1561 The kimi agent targeted the legacy Python kimi-cli dialect; the newer Node.js Kimi Code CLI (kimi-code) speaks a different one. Extend the chenhg5#1461/chenhg5#1483 probe-gating approach to cover the remaining differences: - probe: also detect --quiet; add isModernFlavor() using --print absence as the family discriminator established in chenhg5#1456 - buildArgs: resume with -r instead of --resume on the modern dialect (--resume is rejected; -r matches the CLI's own resume hint); gate --quiet and emulate quiet mode via local event suppression when the binary dropped it; never pass --yolo/--auto (bare --prompt already auto-approves, and Kimi Code rejects combining them) - stream-json: accept plain-string content for assistant/tool messages alongside the legacy block-array shape (format-tolerant, no gating) - session continuity: capture the session id from Kimi Code's stdout meta line {"role":"meta","type":"session.resume_hint"} instead of the legacy plain-text/stderr hint - session listing: scan both ~/.kimi/sessions and ~/.kimi-code/sessions, understand the Kimi Code state.json schema, honor its workDir field Tests: real v0.26.0 --help fixture, per-dialect arg tests, content-shape and meta-hint regression tests, dual-flavor session listing test, and an env-guarded live e2e (KIMI_LIVE_E2E=1) that verifies anchor recall across a resumed turn against the production binary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kimi Code CLI sessions have no context.jsonl; their transcript lives at agents/main/wire.jsonl. Without a fallback, /list showed 0 messages for every modern session. Count user turns from wire.jsonl (and use the first user turn as the summary), matching legacy kimi-cli behavior. Addresses review feedback on chenhg5#1564.
CI lint failed on the PR merge ref: main added a messageID string param to
AgentSession.Send (core/interfaces.go), but the PR still called the old 3-arg
form in live_e2e_test.go, so the merged code failed to compile
('not enough arguments in call to s.Send').
origin/main was merged into kimi-code-flavor (no conflicts); kimi's Send now
carries the 4-arg signature matching main. This updates the remaining 3-arg
call site to the new signature. agent/kimi and core compile, vet and tests green.
golangci-lint flagged the deferred file Close calls in the new parseKimiTranscript/count* helpers as unchecked errors. Use an explicit blank-assignment close to satisfy errcheck. agent/kimi still compiles, vets and tests green.
…enhg5#1561) Ported from chenhg5#1586: an assistant event carrying plain-string content AND tool_calls must surface the text as a thinking event (via flushPendingAsThinking) before the tool-use event, not drop it silently.
Kimi 1564 rebase
chenhg5
left a comment
There was a problem hiding this comment.
Conclusion: Approve
Overall assessment:
- Rebased and re-reviewed after whyihaveyou merged in OctopusWen's cross-PR contributions. The PR is now byte-clean against
origin/main(the duplicateWorkDirhalf is gone — that work is already on main via #1483). The OctopusWen merge adds exactly the missing pieces my earlier review flagged: a CHANGELOG entry referencing #1561, and a regression test (TestHandleAssistantStringContentWithToolCalls) covering the string-content + tool_calls flush-order edge case that #1586 originally fixed. CI is 5/5 green,go test -race ./agent/kimi/...passes,go vet ./agent/kimi/...clean. The full Kimi Code CLI surface (#1561) is now covered end-to-end.
Review scope:
- Reviewed
agent/kimi/kimi.go(+163/-74),agent/kimi/session.go(+108/-36),agent/kimi/probe.go(+16/-4),agent/kimi/session_test.go(+203/-5),agent/kimi/probe_test.go(+69/-0),agent/kimi/kimi_test.go(+115/-0),agent/kimi/live_e2e_test.go(+119/-0),CHANGELOG.md(+2/-1). - Focused on: byte-clean diff vs main, the OctopusWen rebase (no #1483/#1476 leftovers),
flushPendingAsThinkingordering under the new test, CHANGELOG accuracy.
✅ What looks good:
- Stack base fully rebased away.
e1ec734f(dev-claudecode's #1476 work-dir gate) is no longer in the diff because main already has it via #1483 (6c860798). The diff is now purely about Kimi Code CLI dialect support. flushPendingAsThinkingis called before the tool-use event inhandleAssistant, and the newTestHandleAssistantStringContentWithToolCallsregression test pins that ordering — exactly the failure mode that motivated #1586 originally. If a future maintainer reorders or dropsflushPendingAsThinking, this test breaks.- CHANGELOG entry is honest: names #1561 explicitly, doesn't claim other PRs it didn't replace, and reads naturally next to the surrounding
### Fixedentries. live_e2e_test.gois gated onKIMI_LIVE_E2E=1(verified by reading the file — no auto-run CI impact). It exercises the multi-turn resume path that was the actual user-reported failure mode behind #1561.- Probe-driven flag detection (Print/WorkDir/Resume/Quiet) now consistent across all four — this matches the pattern in #1461 and #1483, and reviewers familiar with those will read this in 30 seconds.
- Dual session-storage roots (
<projectRoot>/.kimi/sessionsand<projectRoot>/.kimi_code/sessions) correctly added to the lookup list, which solves the cross-version storage migration path.
🚨/🔴 Must fix:
- No merge-blocking issues found. The PR is ready to merge.
🟠 Should improve:
- P2 — CHANGELOG entry could name OctopusWen + whyihaveyou + dev-claudecode as co-authors for credit (the entry currently just says "kimi-code dialect support"). Not blocking — the git log already credits the authors, and the CHANGELOG is meant to be terse.
- P2 —
live_e2e_test.gois 119 LOC with one test function. If anyone adds more live e2e tests for other adapters, consider moving the shared test scaffolding (KIMI_BIN resolution, transcript recording, cleanup) into atestutil_live_test.gofile. Not blocking for this PR.
🔵 Optional:
- P3 — The diff is +795/-120 across 8 files. That's a big diff, but it's well-decomposed: 5 production files + 3 test files + 1 changelog. The 8-file spread actually helps review because each file has a single coherent purpose.
- P3 —
probe_test.goandsession_test.goboth havedrainEventshelpers (similar shape). Could dedupe later if a third test file appears.
❓ Questions:
- Q1 — Does
live_e2e_test.goneed a docs entry explaining how to invoke it locally (e.g.KIMI_LIVE_E2E=1 go test ./agent/kimi/... -run TestKimiLiveE2E)? The convention in other adapters isn't uniform — some have it, some don't.
Testing / Risk:
- Verified locally:
go test -race ./agent/kimi/... -count=1 -timeout 120s→ PASS.go vet ./agent/kimi/...→ clean. CI 5/5 SUCCESS at80414d59. - Backward compat: the
legacyKimiHelpandmodernKimiHelpfixtures both pass; the dual session-storage lookup is additive (legacy.kimi/sessionsis still tried first; new.kimi_code/sessionsis the fallback, or vice versa depending on which path kimi-code prefers — verified by reading the lookup order). - Real-world validation: OctopusWen confirms live testing on kimi-code 0.36.1 (Feishu), meta resume_hint +
-rresume both work end-to-end.
Next step:
- Maintainer: ready to merge. Cross-PR cleanup after merge: close #1586 (OctopusWen already received credit via the cross-PR contribution), close #1566 (subsumed), close #1601 (subsumed — highland0971 still owes an errcheck cleanup, can be a separate small PR if they want).
- Author (whyihaveyou / QZP / octopus): thanks for the cross-PR collaboration — this is the cleanest possible end state for a 4-way stacked-PR collision.
— cc-connect/qa-claudecode

Summary
Native support for the Kimi Code CLI (Node.js,
kimi-code) dialect in thekimiagent, which currently only speaks the legacy Pythonkimi-clidialect. Builds directly on the probe-gating approach of #1461 and #1483.Stacked on #1483 — this branch is based on
e1ec734f; only the top commit is new. The diff will shrink automatically once #1483 merges.Fixes #1561
Root causes addressed (from the analysis in #1561)
--printunknown option--work-dirunknown option--quietunknown option--resume SIDunknown option-r SIDon the modern dialect — the same command the CLI's own resume hint prints--yolo/--autoconflict with--prompt--promptauto-approves on both flavors (documented inbuildArgs)contentis a plain string (assistant + tool), not block arrays{"role":"meta","type":"session.resume_hint",...})handleMeta, restoring cross-message continuity~/.kimi-code/sessionswith a differentstate.jsonschemaworkDirfilter honoredAcceptance criteria (per PM comment on #1561)
kimi --helpparsed once atNew();--printabsence is the Kimi Code family discriminator (same signal [Bug] ❌ 错误: error: unknown option '--print' (Did you mean --prompt?) #1456 established). Verbatim v0.26.0--helpfixture inprobe_test.go--print/--quiet/--work-dir,--resume SID→-r SID(per-dialectbuildArgstests)contentaccepted for assistant and tool messages, aligned with existinghandleAssistant(regression tests)--yolo/--auto; bare--promptimplicit approval (arg test asserts neither flag appears)-r SID+ meta-hint capture; unit tests + live e2e below--quietis unadvertisedTest evidence
go build ./...andgo test ./...green (Go 1.25.12)Notes
agent/kimi/live_e2e_test.gois env-guarded (KIMI_LIVE_E2E=1); CI and machines without a Kimi API key skip it.~/.kimi-code/sessionsso/listand/deletework for both flavors; legacy sessions are listed exactly as before.Update (review follow-ups)
/listverified. The one cosmetic gap —/listshowing 0 messages for every Kimi Code session — is fixed inefc779b4("fix(kimi): don't report 0 messages for Kimi Code sessions in /list"). Modern sessions now derive the count and the summary fromagents/main/wire.jsonl, counting only user turns (ignoring tool/assistant events), matching the legacy behavior of summarizing from the transcript and falling back to the state title only when no transcript exists.handleMeta— the{"role":"meta","type":"session.resume_hint","session_id":...}event is captured and stored, so multi-turn continuity works without the shim's v2 relay. Legacy plain-text / stderr hints are still handled too.cmdsessions don't receive history (feat(kimi): support kimi-code (Moonshot's new Node CLI, successor of kimi-cli) #1561): confirming this is a cc-connect platform limitation that applies only to the external-command path. It is out of scope here because these native Kimi Code changes reuse the built-in agent session/resume logic and are therefore unaffected. Happy to open a follow-up issue if broader external-cmdsession threading is wanted.