Commit graph

719 commits

Author SHA1 Message Date
liruifengv
8b5a25161c
feat(tui): show full text for long bash commands and AskUserQuestion content (#150)
* feat(tui): reveal full bash command on ctrl+o expand

The Bash tool header truncates long commands at 60 chars and the
result renderer never showed the command body, so the full command
was nowhere to be found in the UI. When the user expands the card
with ctrl+o, render the complete multi-line command above the
output.

* feat(tui): wrap long text in AskUserQuestion dialog instead of truncating

Replace single-line truncation with hanging-indent word wrap for the
question prompt, body description, option label, option description,
and submit-tab review entries. Long content now flows onto multiple
rows instead of being cut off with an ellipsis.
2026-05-28 15:54:00 +08:00
liruifengv
76cbf86e20
fix(tui): cap todo panel at 5 rows with overflow indicator (#146)
* fix(tui): cap todo panel at 5 rows with overflow indicator

Pick visible todos around in_progress (recent done + upcoming pending),
append `+N more` when truncated.

* fix(tui): make todo selector order-agnostic

The previous selector assumed todos were grouped as done...pending
when no in_progress existed. On interleaved orderings (the TodoList
tool keeps the model-provided order without normalizing) the before
and after windows skipped most candidates and produced fewer than
five rows. Pick directly from status buckets instead.
2026-05-28 15:17:29 +08:00
_Kerman
52bdc6fced
perf(tui): cache Text instance in ThinkingComponent to avoid per-frame re-wrap (#144) 2026-05-28 14:31:24 +08:00
liruifengv
dad2b87cee
refactor(tui): split kimi-tui God-class into controllers and command modules (#142)
* refactor(tui): extract TasksBrowserController from KimiTUI

将 /tasks 浏览器相关的 16 个方法(~450 行)从 KimiTUI 提取到独立的
TasksBrowserController。KimiTUI 通过 Host 接口传 this 给 controller,
保留 4 个一行委托方法供外部调用点使用。

kimi-tui.ts: 6160 → 5711 行 (-449)

* refactor(tui): extract StreamingUIController from KimiTUI

将 Streaming Flush(15 个方法)和 Live Render Hooks(15 个方法)
共 ~475 行代码从 KimiTUI 提取到 StreamingUIController。这两个区域
之间紧密耦合(flush 方法直接调用 render hooks),合并提取避免循环依赖。

同时迁移了 5 个实例变量:flushTimer, lastFlushAt, pendingAssistantFlush,
pendingThinkingFlush, pendingToolCallFlushIds。

kimi-tui.ts: 5711 → 5268 行 (-443)

* refactor(tui): extract SessionEventHandler from KimiTUI

将 Session Events(39 个方法)和 Background Task Lifecycle(4 个方法)
共 ~960 行代码从 KimiTUI 提取到 SessionEventHandler。包含事件分发、
turn/step 生命周期、thinking/assistant delta 处理、tool call 管理、
compaction 流程、subagent 管理、MCP 状态渲染、background task 事件。

kimi-tui.ts: 5268 → 4316 行 (-952)

* refactor(tui): extract SessionReplayRenderer from KimiTUI

将 Session Replay 的 21 个方法(~410 行)从 KimiTUI 提取到
SessionReplayRenderer。包含会话历史回放水合(snapshot hydration)、
replay record 渲染、thinking/assistant/tool call 回放、skill 激活、
权限变更、审批结果、background task 通知等逻辑。

kimi-tui.ts: 4316 → 3911 行 (-405)

* refactor(tui): extract slash command handlers from KimiTUI

将 Slash Command Handlers 区域(~765 行)从 KimiTUI 提取到两个文件:
- slash-commands.ts (684 行):命令逻辑(plan/yolo/compact/editor/theme/
  model/title/fork/init/login/connect/logout/feedback)
- slash-command-prompts.ts (183 行):UI prompt 函数(platform selection、
  logout provider、feedback input、API key、model selector 等)

prompt 函数拆为独立模块避免 ESM 同模块内部调用 mock 不生效的问题。
测试改为 vi.mock 对应的 prompt 模块。

kimi-tui.ts: 3911 → 3163 行 (-748)

* refactor(tui): extract AuthFlowController from KimiTUI

将 Auth / Model Bootstrap 的 6 个方法(~115 行)从 KimiTUI 提取到
AuthFlowController。包含 refreshAvailableModels、
enterLoginRequiredStartupState、activateModelAfterLogin、
clearActiveSessionAfterLogout、refreshConfigAfterLogin、
refreshConfigAfterLogout。

kimi-tui.ts: 3163 → 3063 行 (-100)

* refactor(tui): clean up dead imports after controller extraction

移除 kimi-tui.ts 和 controller 文件中因代码提取而残留的 55+ 个
无用 import(SDK event types、OAuth 工具函数、catalog 类型等)。

kimi-tui.ts: 3063 → 3005 行 (-58)

* refactor(tui): remove delegate methods and direct controller references

Phase A: 删除 14 个零调用者的纯委托方法
Phase B: 内联 8 个少量调用者委托(调用处直接引用 controller)
Phase C: controller 之间通过 host 上的 controller 引用直接协作,
         不再绕 kimi-tui 中转(SessionEventHandler → tasksBrowserController,
         SlashCommands → authFlow)

kimi-tui.ts: 3005 → 2956 行 (-49)

* refactor(tui): move pickers, config apply, info commands to slash-commands controller

Phase D: 将 pickers(editor/model/theme/permission/settings)、
config apply(applyEditorChoice/applyThemeChoice/applyPermissionChoice/
performModelSwitch/persistModelSelection)、info commands
(showUsage/showStatusReport/showMcpServers + load* helpers)共 ~400 行
从 kimi-tui.ts 搬到 slash-commands controller。

Phase E: handleBuiltInSlashCommand 中所有 case 直接调用 slashCommands.*,
删除全部 13 个 slash command 委托方法。测试改为直接调用 controller 函数。

kimi-tui.ts: 2956 → 2566 行 (-390)

* refactor(tui): final cleanup — remove last delegates and dead imports

删除 finalizeTurn 和 activateModelAfterLogin 委托方法,
slash-commands 直接通过 host.streamingUI / host.authFlow 调用。
清理 15 个因搬运产生的 dead import。

kimi-tui.ts: 2566 → 2545 行 (-21)

* refactor(tui): clean up dead imports and empty import blocks

清理第二轮搬运后残留的 19 个 dead import 和 3 个空 import 块
(api-key-input-dialog、feedback-input-dialog、settings-selector、
feedback constants 等已搬到 slash-commands controller)。

kimi-tui.ts: 2545 → 2517 行 (-28)

* refactor(tui): inline last auth delegate methods

将 refreshAvailableModels 和 enterLoginRequiredStartupState 的
调用方改为直接访问 this.authFlow.*,删除最后两个委托方法。

kimi-tui.ts: 2517 → 2510 行 (-7)

* refactor(tui): move slash command dispatch to slash-commands controller

将 executeSlashCommand(~45 行)和 handleBuiltInSlashCommand(~80 行)
从 kimi-tui.ts 搬到 slash-commands controller。kimi-tui.ts 的
handleUserInput 现在只做空检查、replay guard、历史持久化,
然后调 slashCommands.dispatchInput(this, text)。

kimi-tui.ts: 2510 → 2367 行 (-143)

* refactor(tui): clean up 38 dead imports and 3 dead interfaces

移除因前几轮搬运残留的 import(message-replay、mcp-server-status、
background-*-status、hook-result-format、event-payload utils、
streaming/theme constants 等)和 SessionUsageResult / RuntimeStatusResult /
ManagedUsageResult 接口定义。

kimi-tui.ts: 2367 → 2311 行 (-56)

* refactor(tui): deduplicate combineStartupNotice and isOAuthLoginRequiredError

将 combineStartupNotice(kimi-tui.ts + auth-flow.ts 各一份)和
isOAuthLoginRequiredError 统一到 constant/kimi-tui.ts,两边改为 import。

kimi-tui.ts: 2311 → 2296 行 (-15)

* refactor(tui): move startup utils out of constant/kimi-tui

combineStartupNotice 和 isOAuthLoginRequiredError 是工具函数不是常量,
从 constant/kimi-tui.ts 移到 utils/startup.ts。

* refactor(tui): remove last session event/replay delegate methods

删除 handleEvent、startSessionEventSubscription、
hydrateTranscriptFromReplay 三个委托方法。
kimi-tui 内部和 auth-flow controller 改为直接访问
sessionEventHandler.startSubscription() 和
sessionReplay.hydrateFromReplay()。
测试改为通过 driver.sessionEventHandler 直接调用。

kimi-tui.ts: 2296 → 2278 行 (-18)

* refactor(tui): reduce TUIState fields by removing redundancy and merging pairs

Delete 4 redundant fields (AppState.yolo, AppState.isStreaming,
TUIState.backgroundAgents, TUIState.assistantStreamActive) that were
derivable from existing state. Merge 4 pairs of always-coupled fields
into single objects (streamingBlock, subagentInfo, activitySpinner,
activeDialog). Demote 3 fields only used by KimiTUI itself to private
class fields. Net reduction: AppState 22→20, TUIState 55→48 fields.

* refactor(tui): extract TUIState types and move streaming state into StreamingUIController

Break the type-level circular dependency where controllers imported
TUIState from kimi-tui.ts while kimi-tui.ts imported runtime values
from controllers. Shared types now live in tui-state.ts (TUIState,
createTUIState) and types.ts (KimiTUIOptions, LoginProgressSpinnerHandle,
etc.) so the import graph is strictly unidirectional.

Move 12 streaming-related fields (currentTurnId, currentStep,
assistantDraft, thinkingDraft, streamingBlock, activeThinkingComponent,
activeCompactionBlock, activeToolCalls, streamingToolCallArguments,
pendingToolComponents, pendingAgentGroup, pendingReadGroup) from the
flat TUIState bag into StreamingUIController as instance properties,
along with 3 dispose methods. TUIState shrinks from 40+ to 28 fields.

* refactor(tui): split slash-commands.ts by domain into 4 focused modules

Extract the 1223-line slash-commands.ts into domain-specific files:

- auth-commands.ts (~350 lines): login, logout, connect, OAuth flows
- config-commands.ts (~380 lines): plan, yolo, compact, model/theme/
  editor/permission pickers and apply logic
- info-commands.ts (~185 lines): feedback, usage, status, mcp reports
- session-commands.ts (~105 lines): title, fork, init

slash-commands.ts is now a slim routing hub (~210 lines) that owns
SlashCommandHost, dispatchInput, and the builtin command switch. All
public handlers are re-exported so existing consumers are unaffected.

* refactor(tui): move command handlers from controllers/ to commands/

Relocate the 6 command-related files into the existing commands/
directory where parsing and routing logic already lives:

  controllers/slash-commands.ts       → commands/dispatch.ts
  controllers/slash-command-prompts.ts → commands/prompts.ts
  controllers/auth-commands.ts        → commands/auth.ts
  controllers/config-commands.ts      → commands/config.ts
  controllers/info-commands.ts        → commands/info.ts
  controllers/session-commands.ts     → commands/session.ts

controllers/ now contains only state/lifecycle controllers
(auth-flow, session-event-handler, session-replay, streaming-ui,
tasks-browser). commands/index.ts re-exports all public symbols.

* refactor(tui): move background/render-dedup state into SessionEventHandler

Move 7 fields from TUIState into SessionEventHandler as instance
properties: backgroundAgentMetadata, backgroundTasks,
backgroundTaskTranscriptedTerminal, subagentInfo,
renderedSkillActivationIds, renderedMcpServerStatusKeys,
mcpServerStatusSpinners.

Add resetRuntimeState() to SessionEventHandler that clears all 7
fields in one call. TUIState shrinks from 28 to 21 fields.

TasksBrowserHost.backgroundTasks is now a top-level ReadonlyMap
property instead of being embedded in the state subset, with a
getter on KimiTUI that delegates to sessionEventHandler.

* refactor(tui): encapsulate StreamingUIController internal state behind semantic methods

Make 12 fields private and expose 18 semantic methods instead of letting
SessionEventHandler, SessionReplayRenderer, and KimiTUI directly
manipulate internal maps and counters. Key methods: registerToolCall(),
accumulateToolCallDelta(), completeToolResult(), markStepTruncated(),
appendThinkingDelta(), appendAssistantDelta(), cleanupAfterReplay().

* refactor(tui): extract EditorKeyboardController from KimiTUI

Move editor callback wiring, pending-exit state, clipboard media
handling, and external editor logic into a dedicated controller.
KimiTUI drops from 2070 to 1814 lines.

* refactor(tui): clean up kimi-tui.ts — strip noise comments, reorganize sections

Remove ~70 single-line comments that merely restated the method name.
Condense multi-paragraph inline comments (signal handlers, start()) to
one-liners that capture the WHY. Reorganize sections: merge the one-
method "Layout" section into Lifecycle, rename "Startup Helpers" to
"Autocomplete & Skill Commands", move stray accessors into "State &
Accessors", delete the empty trailing section, relocate input-history
helpers next to each other. 1814 → 1639 lines.

* refactor(tui): route TUIState field mutations through host methods

Controllers may still read host.state freely, but all direct field
assignments now go through setter methods on the host: setStartupReady,
clearQueuedMessages, shiftQueuedMessage, pushTranscriptEntry,
setExternalEditorRunning, setTasksBrowser. This prevents controllers
from silently mutating shared state without KimiTUI's knowledge.

* chore(tui): drop merge analysis docs, add changeset, fix 2 lint errors

- delete docs/refactor-kimi-tui-{analysis,merge-plan}.md (working notes)
- add changeset for the kimi-tui split refactor
- session-event-handler.ts: drop unused `state` destructure in finishCompaction
- editor-keyboard.ts: void-mark fire-and-forget session.cancel() promise

* chore(changeset): simplify wording
2026-05-28 14:14:20 +08:00
liruifengv
50251a1360
fix(approval): show file content/diff and open full-screen preview on ctrl+e (#139)
* fix(approval): include file content and diff in approval display

After #26 the WriteTool/EditTool input display was reduced to
`{kind: 'file_io', operation, path}`, dropping the args carried by the
previous generic fallback. The approval panel then only had a path to
show — no file content for Write, no diff hunk for Edit — and ctrl+e
expanded to the same one-liner.

Extend the file_io display with optional `content` / `before` / `after`
fields so Write can attach its full content and Edit can attach its
old_string/new_string hunk. The adapter promotes file_io+content to a
file_content block and file_io+before/after to a diff block, matching
what the panel renders for the legacy generic-fallback path.

* feat(tui): open full-screen viewer for approval previews

Inline ctrl+e expand-in-place inflated the approval panel past one
viewport for any non-trivial Edit / Write, which collided with pi-tui's
inline differential renderer and the terminal's "snap to bottom on
stdout" reflex: scrolling back glitched and the screen flickered. On
top of that, the diff renderer's O(m·n) LCS DP ran every frame the
panel was visible, so each spinner tick re-paid the cost.

Make ctrl+e hand off to a dedicated full-screen viewer instead. The
viewer renders all body lines once at construction and slices them on
scroll, so per-frame cost is O(viewport) regardless of payload size.
It uses the same nested-takeover pattern as TaskOutputViewer; the
approval panel instance is preserved and refocused on close so the
selection / feedback state survives.

The panel itself drops its local `expanded` toggle and always renders
the compact cluster view; ctrl+e now exclusively forwards to the host
when there is something to preview, and falls through to the existing
plan-expand toggle otherwise.

* chore(changeset): restore approval previews
2026-05-28 13:22:33 +08:00
liruifengv
16e881ed64
docs(changelog): sync 0.4.0 from apps/kimi-code/CHANGELOG.md (#125) 2026-05-27 23:31:03 +08:00
github-actions[bot]
fa114c150d
ci: release packages (#93)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-27 22:50:19 +08:00
qer
ebf6e8181e
feat: add plugin manager and official plugins (#119)
* feat: add plugin manager and official plugins

* fix(agent-core): honor plugin capability overrides

* fix: restrict plugin zip root detection

* Update apps/kimi-code/src/constant/app.ts

Co-authored-by: liruifengv <liruifeng1024@gmail.com>
Signed-off-by: qer <wbxl2000@outlook.com>

---------

Signed-off-by: qer <wbxl2000@outlook.com>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-05-27 22:47:33 +08:00
liruifengv
2c7a8cc010
feat(tui): expand paste markers on second paste (#116)
* feat(tui): expand paste markers on second paste

When the cursor sits on a folded paste marker (e.g. `[paste #1 +15 lines]`)
and the user pastes again (Ctrl-V or bracketed paste), the marker is expanded
back to its original content instead of inserting new clipboard data.

* chore: add changeset for paste marker expansion

* fix(tui): preserve paste content after marker expansion for undo

Stop deleting the paste entry from the Map after expansion so that
undo → re-expand still works.

* fix(tui): buffer consumed paste data to handle split end sequences

Accumulate chunks while consuming discarded paste data so a
split ESC[201~ across chunks still resets consumingPaste.
2026-05-27 20:19:18 +08:00
_Kerman
2b74025302
feat: rework permission decision policies (#26) 2026-05-27 20:07:24 +08:00
liruifengv
b7e7404d90
docs: document /export-md and /export-debug-zip slash commands (#114)
Add reference table entries for the new in-TUI export commands, and
extend the Sessions guide so users can discover them alongside the
existing `kimi export` CLI subcommand. Mirrored across en and zh.
2026-05-27 19:15:14 +08:00
liruifengv
028d069b12
feat(tui): add /export-md slash command (#113)
* feat(tui): add /export-md slash command

Add a new /export-md (alias: /export) command that exports the current
session conversation as a human-readable Markdown file. The export
includes YAML frontmatter metadata, an overview section, and
turn-by-turn dialogue with collapsible thinking blocks and tool
call/result details.

Also exposes Session.getContext() on the SDK to allow TUI-layer access
to the agent's conversation history.

* chore: add changeset for /export-md

* fix: use static imports instead of dynamic imports in handleExportMdCommand

* chore: simplify changeset wording
2026-05-27 18:51:44 +08:00
liruifengv
d03f6f4fa5
feat(tui): add /export-debug-zip slash command (#112)
* feat(tui): add /export-debug-zip slash command

Add a new built-in slash command that exports the current session as a
debug ZIP archive directly from the TUI, mirroring the existing
`kimi export` CLI behavior.

Extract `detectShellEnvironment` into a shared utility to eliminate
duplication between the CLI export handler and the new TUI command.

* chore: add changeset for /export-debug-zip

* refactor: extract toTerminalHyperlink into shared utility

Make the exported ZIP path clickable in terminals that support OSC 8
hyperlinks (iTerm2, Terminal.app, VS Code, etc.). Also deduplicate
the helper that was copy-pasted in plan-box.ts.
2026-05-27 17:56:21 +08:00
_Kerman
8b5065c08b
ci: add pkg.pr.new previews (#95) 2026-05-27 17:09:33 +08:00
happy wang
2e8c417818
fix(tui): stop thinking spinner leaking past turn end on empty deltas (#97)
* fix: stop thinking spinner leaking past turn end on empty deltas

When a provider emits an empty thinking delta (e.g. Anthropic
signature_delta -> think: ""), a ThinkingComponent was created with a
running spinner but thinkingDraft stayed empty. Subsequent calls to
flushThinkingToTranscript guarded on thinkingDraft.length and
returned early without calling onThinkingEnd(), leaking the spinner
past the turn end.

Two fixes:
- onThinkingUpdate: skip component creation for empty text when no
  existing component needs updating (source prevention).
- flushThinkingToTranscript: finalize any orphaned component even
  when thinkingDraft is empty (defensive cleanup).

* chore: add changeset for thinking spinner fix

* refactor(tui): simplify flushThinkingToTranscript

---------

Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-05-27 14:48:18 +08:00
_Kerman
d1c381f38a
test(agent-core): consolidate test helpers into AgentTestContext (#106) 2026-05-27 14:41:45 +08:00
liruifengv
d599183c8e
feat(export): record install source and shell environment in manifest (#105)
* feat(export): record install source and shell environment in manifest

Capture how the CLI was installed (npm-global, native, etc.) and the
user's terminal environment (TERM, TERM_PROGRAM, multiplexer, SHELL)
so exported session archives carry richer diagnostic context.

* chore: add changeset for export manifest enhancements

* chore: simplify changeset description
2026-05-27 14:39:52 +08:00
qer
55870616ca
feat: expose LLM stream timing events (#101)
* feat: expose LLM stream timing events

* feat(kimi-code): show LLM timing in debug mode (KIMI_CODE_DEBUG=1)

* refactor: extract debug timing formatting into standalone module

* chore: remove changeset

---------

Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-05-27 13:46:57 +08:00
_Kerman
73c4232e71
fix: show original session command after fork (#103) 2026-05-27 13:38:25 +08:00
_Kerman
6f55f1d0af
fix(agent-core): route session logs exclusively to session sink (#102) 2026-05-27 13:00:17 +08:00
liruifengv
ce420bf1c6
refactor(tui): unify resume replay rendering (#88)
* fix(tui): render rejected plan reviews in plan box

* fix(tui): keep final approved plan in replay

* refactor(tui): unify resume replay rendering
2026-05-27 12:44:30 +08:00
liruifengv
49ca317326
docs(changelog): sync 0.3.0 from apps/kimi-code/CHANGELOG.md (#100)
* Update SKILL.md

* docs(changelog): sync 0.3.0 from apps/kimi-code/CHANGELOG.md
2026-05-27 12:24:45 +08:00
_Kerman
e5717b7261
refactor: unify path normalization with pathe (#84) 2026-05-27 11:53:24 +08:00
caiji
61cae592fa
fix(kimi-code): guard footer PR lookup spawn failures (#89)
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-05-27 11:42:11 +08:00
_Kerman
4e458d6364
refactor: share LLM retry classification (#92) 2026-05-27 11:20:26 +08:00
github-actions[bot]
cef5efc619
ci: release packages (#65)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-26 22:27:24 +08:00
qer
2bb50a38d8
fix: avoid local completion budget truncation (#85) 2026-05-26 22:25:15 +08:00
_Kerman
5e354d0cc8
fix(kimi-code): show last-request token count for running subagents (#86) 2026-05-26 22:06:58 +08:00
qer
5a90b53b04
fix: avoid duplicate plan mode setup (#79)
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-05-26 20:58:10 +08:00
7Sageer
7d9216d5aa
fix(agent-core): ensure tool.call always has paired tool.result (#83)
* fix(agent-core): always pair tool.call with tool.result on malformed returns

A tool returning undefined, a primitive, or an object without a valid
output field crashed normalization, exited the batch loop without
dispatching the matching tool.result, and left the next provider
request to fail with a missing tool_call_id response.

- Validate the tool return at the boundary in runRunnableToolCall via a
  new coerceToolResult helper; malformed returns become an isError
  result.
- Harden normalizeToolResult and toolResultStopsTurn against
  non-conformant input from synthetic results or finalize hooks.
- Track unpaired tool.call ids in runToolCallBatch and emit
  compensating error results in finally for any that did not receive a
  result.

* refactor(agent-core): simplify tool result pairing

Drop the unpairedCallIds tracking and finally-compensation wrapper from
runToolCallBatch. The set was updated before dispatchEvent, so a failure
between the two left it inconsistent; the per-call try/catch already
produces a paired error result, making the outer wrapper redundant.

Coerce hook returns at the synthetic and finalizeToolResult boundaries
so a malformed hook output is normalized into a paired error result the
same way a malformed tool return already is.
2026-05-26 20:14:54 +08:00
liruifengv
fe60c215be
fix(kimi-code): exit promptly when the controlling terminal dies (#77)
* fix(kimi-code): exit promptly when the controlling terminal dies

Register SIGHUP/SIGTERM handlers and stdout/stderr 'error' listeners on
KimiTUI startup. SIGHUP and EIO/EPIPE/ENOTCONN writes both indicate the
terminal is gone; under those conditions the normal cleanup path keeps
writing restore sequences that re-fire the same EIO and can pin a CPU
core indefinitely. The new emergencyTerminalExit() path skips cleanup
and exits 129 immediately. SIGTERM still routes through the graceful
stop() path so telemetry and session state flush normally.

* fix(kimi-code): emergency-exit when SIGTERM graceful shutdown rejects

The SIGTERM handler previously did `void this.stop()`, so a rejection
from `closeSession()` or `harness.close()` was swallowed and could leave
the process hanging on pending I/O (the latch on `isShuttingDown` also
makes any further graceful exit attempt a no-op). Chain a `.catch()` so
a failed graceful shutdown falls through to `emergencyTerminalExit()`
and the process exits with 129 instead of waiting for SIGKILL.

* fix(kimi-code): preserve 128+signum exit code for signal-driven shutdown

Installing a SIGTERM listener suppresses Node's default termination,
which would have exited 143. With the previous handler, externally
terminated TUIs routed through `onExit()`'s default `exitCode = 0` and
reported success to supervisors keying off non-zero on signal exits.

Thread an optional `exitCode` through `stop()` → `onExit()`, parameterize
`emergencyTerminalExit()`, and have the SIGTERM handler pass 143 down
both the success and failure paths. SIGHUP and dead-terminal write
errors still exit 129 (the SIGHUP convention). User-initiated `/exit`
keeps its 0 default since `stop()` is called without an `exitCode`.

* fix(kimi-code): unregister signal handlers when start() throws

Signal and stdout/stderr 'error' listeners were installed at the top of
start() but never rolled back when initMainTui() or finishStartup()
threw. Retrying start() in the same Node process (tests, embedded use)
accumulated listeners on `process` and would eventually trip
MaxListenersExceededWarning or run shutdown logic multiple times.

Wrap the whole start() body in a try/catch so any thrown error
unregisters the listeners before propagating. Inner catch blocks still
own focus/theme/UI cleanup; this only adds the listener half.
2026-05-26 20:03:52 +08:00
7Sageer
61f7d0e7a2
fix(kosong): make OpenAI-compatible thinking work without reasoning_key (#78)
* fix(kosong): make OpenAI-compatible thinking work without reasoning_key

Reasoning field names (reasoning_content / reasoning_details / reasoning)
are protocol facts, not user preferences. Treating reasoning_key as a
required user-set field meant any path that didn't go through the catalog
— hand-written config.toml in particular — silently lost thinking content
and broke strict gateways like DeepSeek.

Demote reasoning_key to an internal protocol constant with an explicit
override:

- Inbound (stream + non-stream): scan reasoning_content,
  reasoning_details, reasoning in order; first string value wins. An
  explicit reasoning_key restricts the scan to that one field.
- Outbound: serialize ThinkPart back as reasoning_content by default.
  An explicit reasoning_key writes to that field instead.
- reasoning_effort auto-injection no longer requires reasoning_key;
  presence of ThinkPart in history is enough.

Catalog plumbing is unchanged — explicit values from the catalog still
win, the default just stops being undefined.

Manually verified end-to-end against the real DeepSeek API with a
hand-written config.toml that does not set reasoning_key: thinking
content renders, no 400, multi-turn conversations work.

* fix(kosong): normalize blank reasoning_key to unset

ModelAliasSchema accepts `reasoning_key = ""` (z.string().optional()).
A blank value used to disable the default field scan and route both
inbound reads and outbound writes through an empty property name.
Trim and treat empty as undefined at the provider boundary so the
default protocol behavior applies.

* fix(kosong): preserve caller-pinned reasoning_effort during auto-inject

When the history contains ThinkPart, generate() injects
reasoning_effort='medium' and then assigns it onto createParams,
which used to silently overwrite a value the caller set via
withGenerationKwargs({ reasoning_effort: 'high' }). Skip auto-inject
when an explicit reasoning_effort already lives in kwargs.
2026-05-26 19:28:25 +08:00
liruifengv
1fbefc9939
fix(tui): Improve the Write tool UX. (#81)
* fix(tui): cap Write preview at args-finalize to preserve terminal scrollback

The Write tool preview rendered the full file content between args-finalize
and result, then snapped to the 10-line collapsed cap once the result landed.
That large shrink triggered pi-tui's full-redraw path, which emits \x1b[3J
and wipes the terminal scrollback (including history from before TUI start).

Cap the preview as soon as args finalize so the rendered height stays stable
across the args-finalize → result transition, avoiding the full redraw.

* chore: update changeset message
2026-05-26 19:16:13 +08:00
7Sageer
6f22ae48f8
feat(kimi-code): rework /logout into a provider picker and add /disconnect alias (#76)
* feat(kimi-code): pick provider when running /logout

/logout used to clear the credential tied to the currently selected
model, which made the target invisible and coupled logout semantics to
model selection. It now opens a picker over every credential currently
held and highlights the active model's provider so pressing Enter
matches the previous behavior.

* feat(kimi-code): alias /logout as /disconnect

So users who entered a provider via /connect can leave it with the
symmetric /disconnect.

* fix(kimi-code): keep /logout usable for stale managed config and unrelated providers

- Surface the Kimi Code OAuth entry in the picker whenever
  config.providers still references it, not only when a live token is
  present. auth.logout cleans the config either way, so stale entries
  can no longer become unreachable through /logout.
- When the picked provider is not the one backing the current model,
  just refresh the provider/model listing and leave the session and
  model selection alone. Full teardown stays for the case where the
  active provider was the one being removed.
2026-05-26 19:12:04 +08:00
Kai
c0b63c1ea7
refactor(vis): rewrite for new agent-core protocol (#34)
* feat(agent-core): re-export wire record types for in-monorepo consumers

* chore(vis): purge legacy wire protocol code

* feat(vis): introduce single-source agent-record types

* test(vis): add fixture session and builder helper

* feat(vis): implement new session store reader

* feat(vis): wire new session list/detail routes

* refactor(vis): drop legacy path config

* feat(vis): adapt session list page to new DTO

* feat(vis): implement per-agent wire reader

* feat(vis): rewrite wire route for new protocol

* feat(vis): rewrite wire type metadata for new protocol

* feat(vis): rewrite wire row + headline for new record union

* feat(vis): wire tab detail panel + multi-agent selector

* feat(vis): rebuild wire issues detection for new protocol

* feat(vis): implement context projector

* feat(vis): rewrite context route on projector

* feat(vis): rebuild context tab for new ContextMessage shape

* feat(vis): implement agent tree builder

* feat(vis): rewrite agents route

* feat(vis): rebuild subagents tab around state.json.agents

* feat(vis): rebuild state tab on raw state.json

* chore(vis): purge residual legacy field references

* vis: rewrite complete on new agent-core protocol

* fix(vis): adapt to wire protocol 1.1 with flattened tool calls

* fix(vis): populate workDir from session index

* fix(vis): return broken-state sessions from detail lookup

* fix(vis): tolerate per-session wire read failures during listing

* fix(vis): read wire files from canonical session path

* feat(vis): restore session detail page with full tab layout

* feat(vis): wire subagent context tab to real ContextTab

* fix(vis): sync wire and context tab agentId with prop changes

* feat(vis): auto-pick a free dev port when 3001 is busy

* feat(vis): accept v1.0 wire files via agent-core migration chain

* refactor(vis): default API to 5174 and pick a non-colliding vite port

* feat(vis): make long strings expandable with copy in JsonViewer

* fix(vis): stop gating session health on protocol version

* refactor(vis): split wire records into raw + projected; best-effort unknown protocol

* feat(vis): pair tool.call with tool.result via inline cross-reference and hover highlight

* feat(vis): open session folder and copy its path from the detail header

* fix(vis): reconstruct assistant and tool messages from loop events

* fix(vis): keep system prompt bubble within the message column width

* feat(vis): collapse tool result bubble by default in context view

* feat(vis): expose broken_main_wire in the session health filter

* fix(vis): reset wire and context tab agent when navigating sessions

* fix(vis): fall back to a generic headline for unknown wire record types

* fix(vis): emit compaction summary as an assistant message with origin

* fix(vis): harden session-store reads for broken wires, broken state, and path-traversal agent ids

* feat(vis): inline image previews for image_url content parts
2026-05-26 17:57:49 +08:00
happy wang
0ce0072cb4
fix(agent-core): resolve user skills from OS home directory, not kimi home (#72)
* fix(agent-core): resolve user skills from OS home directory, not kimi home

KimiCore incorrectly used the kimi home directory (e.g. ~/.kimi-code) as
userHomeDir, causing the skill scanner to look for user skills under
~/.kimi-code/.agents/skills/ instead of ~/.agents/skills/. Only the
builtin mcp-config skill was found.

Fix by always setting userHomeDir to homedir() (the actual OS home),
independent of the homeDir / KIMI_CODE_HOME options that control where
session data is stored.

* chore: add changeset for skill user home dir fix

* test(node-sdk): align SDK skill test with OS home resolution fix

* Apply suggestion from @liruifengv

Signed-off-by: liruifengv <liruifeng1024@gmail.com>

---------

Signed-off-by: liruifengv <liruifeng1024@gmail.com>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-05-26 17:50:38 +08:00
liruifengv
bddc60f0e9
fix(tui): block model and session commands while busy (#73) 2026-05-26 17:44:14 +08:00
HynoR
d95b01342a
fix(catalog): preserve reasoning fields in custom model (#70)
* fix(catalog): preserve reasoning fields

* fix(catalog): treat interleaved=true as reasoning_content

models.dev documents `interleaved` as `boolean | { field }`, where the
bare boolean means "general support" without an explicit field name.
The previous branch returned undefined for `true`, leaving openai-compat
gateways that publish `interleaved: true` without a round-tripped
reasoning field. Map `true` to the default `reasoning_content` so those
models still surface and replay thinking content.

* fix(catalog): preserve interleaved field in built-in catalog snapshot

`update-catalog.mjs` drives the bundled catalog that ships with release
builds and is the default source for `/connect`. The allowlist dropped
`interleaved`, so even after the runtime learned to read the field, the
default offline path never sees it — reasoning round-tripping silently
stayed off for openai-compat models in release builds. Keep
`interleaved` so the bundled snapshot carries the same metadata as the
live models.dev catalog.

---------

Co-authored-by: 7Sageer <7sageer@djwcb.cn>
2026-05-26 17:32:37 +08:00
liruifengv
8fb61f9a3e
fix: hide completed todos on resume (#57) 2026-05-26 16:18:19 +08:00
7Sageer
8ddfc0433e
fix(kimi-code): skip masking empty API key input to avoid ghost dot (#66) 2026-05-26 16:02:49 +08:00
_Kerman
e2b2b46fc9
refactor(agent-core): make AgentRecords hold the Agent instance directly (#62) 2026-05-26 15:52:26 +08:00
liruifengv
0bcbf167fe
docs(changelog): sync 0.2.0 from apps/kimi-code/CHANGELOG.md (#61) 2026-05-26 15:27:13 +08:00
github-actions[bot]
d496fd40b6
ci: release packages (#42)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-26 15:03:28 +08:00
_Kerman
cf2227e8a5
fix: warn instead of error on newer wire protocol version (#49)
* fix: warn instead of error on newer wire protocol version

* fix

* fix test
2026-05-26 14:56:32 +08:00
liruifengv
064343a6e5
fix: correct X-Msh-Platform header value to kimi_code_cli (#52) 2026-05-26 14:39:19 +08:00
7Sageer
c473823a4b
feat(kimi-code): use positional args and bare keywords for /connect (#50)
Replace the --flag style (/connect --url=..., /connect --refresh)
with positional arguments and bare keywords:

- /connect https://example.com/catalog.json
- /connect refresh
- /connect (no args, default behavior)

This aligns the slash-command UX with peer CLI tools (claude-code,
pi, opencode) where slash commands use bare words rather than
CLI-style flags.

Closes #30 follow-up
2026-05-26 14:24:42 +08:00
7Sageer
cf5270ce6a
docs: document /connect and trim its changeset wording (#48)
* docs: document /connect command and model catalog

Add a /connect row to the slash-commands reference (English and Chinese)
and a new "/connect and the model catalog" section in providers.md that
covers the bundled catalog, --refresh, --url, and the relationship with
hand-written config.toml entries.

* chore(changeset): trim /connect changesets to user-facing wording

Drop internal phrasing (pruned snapshot, "not gated by models.dev",
fallback mechanics, and the Anthropic catalog base URL path fix that
landed alongside the initial /connect implementation) so the release
notes describe behavior users can observe, not implementation details.

* docs(zh): translate "catalog/flag/endpoint" in /connect section

Rework the Chinese wording for the /connect docs: render "model catalog"
as "模型目录" (with the English term in parentheses on first use), drop
the leftover English "flag"/"endpoint" usage, and smooth out the
translationese in the surrounding sentences.

Update the cross-link anchor in slash-commands.md to match the new
Chinese heading.

* docs(en): tighten /connect wording

Fix the subject/verb mismatch in the /connect step description (the
sentence switched subjects mid-list), replace "endpoint" with the more
accurate "URL" since the catalog source is a static JSON file, and
clarify that /connect is limited to the provider types in the table
above rather than to whatever the catalog happens to cover.

* chore(changeset): keep connect-model-catalog changelog to one line

Defer --refresh, --url, and metadata details to the new docs section
(`/connect` and the model catalog in configuration/providers.md). The
changelog now just announces the command exists.
2026-05-26 14:19:44 +08:00
Haozhe
07ed2cf9d4
fix(cli): fix first launch telemetry timing and extract telemetry init (#47)
* fix(cli): fix first launch telemetry timing and extract telemetry init (7 files)

- extract telemetry initialization into cli/telemetry.ts\n- ensure deviceId is created before harness construction for correct first_launch tracking\n- emit session resume hint as meta message in stream-json output format
2026-05-26 13:57:22 +08:00
7Sageer
a200a297ac
feat(kimi-code): add /connect command with bundled model catalog (#30)
* feat: add /connect command with models.dev catalog support

Add a /connect slash command that configures a provider and model
from a models.dev-style catalog. Users no longer need to hand-write
model metadata (context window, output limit, capabilities).

Architecture (3 layers):
- kosong: pure data layer — Catalog schema, inferWireType,
  catalogModelToCapability
- node-sdk: IO + config write — fetchCatalog, applyCatalogProvider,
  catalogModelToAlias
- app: TUI flow — /connect command, provider/model selection,
  credential input, config persistence

UI improvements in this PR:
- ChoicePickerComponent: add searchable (fuzzy filter + search bar)
- ModelSelectorComponent: add searchable (same)
- Extract reusable paging.ts for list pagination

Changesets included for kosong, kimi-code-sdk, and kimi-code.

* feat: bundle pruned models.dev catalog for offline /connect

When the network is unavailable, /connect now falls back to a built-in
snapshot of the models.dev catalog.

- `scripts/update-catalog.mjs`: fetches models.dev/api.json, strips
  unnecessary fields, and writes `src/built-in-catalog.ts` with the
  JSON string as a TS constant.
- `loadBuiltInCatalog(text?)` in node-sdk: parses the JSON string safely;
  returns undefined on any failure.
- `handleConnectCommand`: on fetch failure, shows an informative offline
  message and tries the built-in snapshot.
- The snapshot file is a placeholder (`undefined`) in source control;
  `update-catalog.mjs` populates it before release builds so the actual
  catalog is inlined into the bundle by rolldown.

* refactor(tui): share search and pagination across list pickers

ChoicePicker and ModelSelector each carried their own copy of the cursor +
fuzzy-search + pagination state machine. Extract it into a reusable
SearchableList so both pickers share one implementation; behavior is unchanged.

* fix(tui): filter unsupported catalog providers

* docs(tui): clarify /connect stale-alias cleanup depends on removeProvider

* fix(kimi-code): inject built-in catalog at release time

* feat(tui): hint at /login and /connect when /model has no models

Replace the bare "No models configured." error with a notice that
points users to /login for Kimi and /connect for other providers.

* fix(tui): tighten /connect error reporting for edge cases

Two silent-failure cases in /connect could leave users without any
feedback to act on:

- Reject `--url` when its value is missing (e.g. `/connect --url` or
  `/connect --url=`). Previously the argument parser silently fell
  back to the default catalog, so a malformed flag still appeared to
  succeed but with the wrong source.
- Show an explicit error when the resolved catalog yields no providers
  with supported wire types. Previously the picker resolved with no
  selection and the command returned without any UI feedback.

* chore(tui): restore slash invalid intent type

* fix(tui): support /logout for /connect-configured providers

After /connect writes a non-managed provider (e.g. openai), /logout
fell through to "Nothing to logout." because the handler only matched
the managed default and isOpenPlatformId branches, leaving users no
in-app way to drop the API key and model aliases they just configured.

Collapse the OpenPlatform branch into a generic "provider is present
in config" check so any non-managed provider in config — OpenPlatform
OAuth targets and /connect catalog providers — goes through the same
removeProvider path.

* fix(tui): reject --url values that are not http(s) URLs

`resolveConnectCatalogRequest` previously matched any non-space token
after `--url` as the URL, so `/connect --url --refresh` parsed
`--refresh` as the value and bypassed the missing-value error path.
Bare non-URL tokens (`/connect --url not-a-url`) and non-http(s)
schemes were also silently accepted.

Constrain the captured value to `https?://...` so flag-like and
non-URL tokens fall through to the existing `URL_FLAG_PRESENT_RE`
check and surface a clear error.

* ci(native): scope built-in catalog generation to signed macOS jobs

The catalog-generation step ran whenever `inputs.sign-macos` was true,
including Linux and Windows targets that take the local-profile build
path and never consume the generated catalog. A transient models.dev
outage would therefore fail unrelated artifact builds.

Match the condition to the macOS signed release-profile build that
actually consumes the bundled catalog.

* fix(ci): embed built-in catalog in non-macOS native artifacts

The earlier narrowing to `runner.os == 'macOS' && inputs.sign-macos`
relied on a misread of build.mjs: its `profile === 'release'` guard
only auto-fetches the catalog as a dev fallback. Whether the binary
actually embeds the catalog is decided by tsdown's define at bundle
time, which reads KIMI_CODE_BUILT_IN_CATALOG_FILE regardless of
profile.

Linux and Windows release artifacts therefore lost their bundled
catalog and silently regressed offline /connect on those targets.
Restore generation for all OS jobs when sign-macos is true.

* chore(tui): mention /connect in welcome panel hints

Align the welcome panel with the /model picker so the empty-state copy
points users to both /login and /connect.
2026-05-26 12:34:49 +08:00
Fengzc
e9e4a48633
fix(cli): clarify prompt mode login guidance (#38) 2026-05-26 12:13:28 +08:00