feat(hub): map kimi 0.31 wire events — cancel, compaction, plan/swarm/permission modes - #486
Merged
physercoe merged 1 commit intoJul 31, 2026
Conversation
…/permission modes
kimi-code 0.31.0 keeps wire protocol 1.5 (gate unchanged) but adds 10
event types that fell into the mapper's default arm as unknown_type
noise. Map them:
- turn.cancel → turn.result {status:cancelled, stop_reason:cancelled}
(main only, same guard as mapStepEnd). Fixes a cancelled turn
leaving the session stuck busy — kimi writes no end_turn for the
interrupted turn, and mobile's busy-walker had no terminal to find.
Payload mirrors the ACP driver's eager-cancel vocabulary verbatim.
- turn.steer (all origins) + turn.prompt origin=task → explicit,
documented drops (engine-internal notices / input.text duplication).
- context.apply_compaction → one system{subtype:compaction} card with
bounded summary + token delta; begin/complete dropped as bookends.
- plan_mode.*, permission.set_mode, swarm_mode.* → producer=system
cards (no dedicated mode kind exists hub-side; ACP forwards
current_mode_update the same way).
- mcp.tools_discovered → drop (catalog noise).
default: unknown_type drift policy unchanged. Pinned by a redacted
0.31.0 fixture replay (testdata/wire_main_0_31.jsonl); wire_main.jsonl
stays the 0.28.1 pin.
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.
Summary
Updates the kimi-code M4 wire-tail mapper (
hub/internal/drivers/local_log_tail/kimi_code) for the wire event vocabulary added in kimi-code 0.31.0. Verified against live 0.31.0wire.jsonlcaptures on a dev machine (~/.kimi-code/sessions/wd_*/session_*/agents/main/wire.jsonl, 21991-line busy session).1.4 → 1.5 is vocabulary growth, not a flag day. 0.31.0 still writes
protocol_version: "1.5"and every 0.28.x event shape we already parse is byte-identical (content.part, tool.callargs, tool.resultresult.output, step.endfinishReason, usage.record, todo store). TheSupportedProtocolVersionmajor-version gate (any 1.x) is unchanged, as is the launch gate inlaunch_m4_kimi.go. What's new is 10 event types that today fall into the mapper'sdefault:arm and each become a noisysystem{subtype:unknown_type}event (hundreds per busy session — 133turn.steeralone in the sampled wire) while missing real signal — most importantly the cancel terminal.Mapping table implemented
turn.cancelturn.result {status:"cancelled", stop_reason:"cancelled"}(main agent only)step.end end_turnfor an interrupted turn (verified: cancel → next line is the next turn's prompt), so a cancelled turn left the session stuck busy forever. The payload is exactly the ACP driver's eager-cancel vocabulary (driver_acp.goInput("cancel")): mobile's busy-walker terminates on the kind, the hub digest readsstop_reason(cancelled∈normalTurnStopReasons— an intentional end, not an integrity finding), andstatusmatches what every ACP engine posts on cancel, so digest + mobile failure accounting treat both engines identically. Subagent guard mirrorsmapStepEnd(#374).turn.steer(any origin)background_task(129/133 in capture) is the engine's own task-notification envelope — CLI-internal noise.usersteers: same duplication rationale asturn.prompt— hub-originated input arrives via tmux send-keys and was already posted asinput.textby the hub, so mapping would double the bubble; pane-typed steers are the accepted loss, same as pane-typed prompts. Unknown future origins drop quietly. Value overdefault:= no unknown_type noise + a stated decision. No plan-chain side effects (steers redirect the in-flight turn; verified turnId unchanged).turn.promptorigin.kind="task"context.apply_compactionsystem {subtype:"compaction", summary(≤500 chars), tokens_before, tokens_after, compacted_count, text}textline lands on the default renderer); the fields keep structure for a future fold marker. Mirrors claude mapper'scompact_boundaryarm (single producer=system notice, off the busy-inference path). Summary is rune-bounded so one event can't dominate the transcript/event store.full_compaction.begin/full_compaction.completeplan_mode.enter/cancel/exitsystem {subtype:"plan_mode", phase, id?, text}current_mode_updateas a verbatim producer=system frame — so a system subtype is the reuse-consistent shape.exithandled though not yet observed (enter/cancel verified live).permission.set_modesystem {subtype:"permission_mode", mode, text}permission_modeon session.init. Modes observed live: yolo / auto / manual.swarm_mode.enter/exitsystem {subtype:"swarm_mode", phase, trigger?, text}trigger("tool" observed).mcp.tools_discoveredtools.set_active_tools/llm.tools_snapshot.system {subtype:unknown_type}Evidence (local 0.31.0 wire samples)
turn.steer×133 (129background_task+ 4user),turn.prompt×111 (109user+ 2task),full_compaction.*×15 trios,permission.set_mode×5,swarm_mode.*×4 pairs,plan_mode.*×1 pair,turn.cancel×2,mcp.tools_discovered×1,metadata.protocol_version="1.5".llm.request (turn 19.11)→turn.cancel→ next line isturn.prompt (turn 20)— noend_turnfor the interrupted turn, confirming the stuck-busy mechanism.testdata/wire_main_0_31.jsonlholds redacted real lines for every new type (user text replaced with placeholders);wire_main.jsonlstays as the 0.28.1 pin.Tests
mapper_test.goextended per type: exact payload shapes (esp. the cancel →turn.resultvocabulary), explicit drops emit NOTHING (incl. nounknown_type), plan chain (turnSeq/planMsgID) provably unaffected by every new type, subagent guard onturn.cancel, task-origin prompt still re-arms, 500-rune summary truncation, and a 0.31 fixture replay pinning the kind histogram.go test ./internal/drivers/local_log_tail/... ./internal/hostrunner/...— green except 2 hostrunner failures that reproduce identically on pristineorigin/mainon this machine (TestWriteKimiTSMCPConfig_FreshWorkdir— local TermiPod.app browser bridge injection;TestDatasetExportRRD_EndToEndWithAStubExporter— macOS/var→/private/varsymlink). Pre-existing, environment-induced, unrelated to this diff.go vetclean on the touched packages.go test ./...result noted in the PR comments if it differs (the 8TestLintGovernedActions_*failures on macOS bash 3.2 are pre-existing and green in CI).