Skip to content

feat(tui): run interactive shell on agent-core-v2 with deferred session creation#1543

Open
liruifengv wants to merge 5 commits into
kimi-code-v2from
tui-v2
Open

feat(tui): run interactive shell on agent-core-v2 with deferred session creation#1543
liruifengv wants to merge 5 commits into
kimi-code-v2from
tui-v2

Conversation

@liruifengv

Copy link
Copy Markdown
Collaborator

Related Issue

No related issue — this lands the tui-v2 work branch onto kimi-code-v2.

Problem

The interactive TUI still reached the agent engine through the legacy v1 SDK (@moonshot-ai/kimi-code-sdk). To move the TUI onto agent-core-v2, a dedicated facade (apps/kimi-code/src/core/) was introduced.

A follow-up problem with the initial migration: fresh TUI startup eagerly created an empty session just to read initial state (default model, context window, modes). That polluted the session list and forced every startup through the full session bootstrap.

What changed

Two commits:

  1. 250bd460 feat(tui): migrate interactive shell to agent-core-v2

    • New facade apps/kimi-code/src/core/ (CoreHarness at App scope, CoreSession at session scope); the interactive TUI reaches the engine only through this facade. Print (kimi -p), ACP and other subcommands still consume the v1 SDK.
    • Session lifecycle, event fan-in, approval/question pendings, resume/replay, telemetry and config parity with v1 semantics; known v2 gaps are downgraded and marked TODO(v2-gap), documented in src/core/README.md.
  2. 99fe1702 feat(tui): defer session creation to the first message on fresh startup

    • Fresh startup no longer creates a session. The initial render reads config-level defaults (model, context window, permission/plan mode, thinking effort) from the new App-scope-only CoreHarness.getStartupState() (IConfigService + IModelResolver, plus the profile service's own thinking resolution).
    • The session is created lazily on the first user message (sendAfterLazySessionStart); OAuth login-required errors now surface at first message instead of failing startup.
    • Model switches without a live session (activateModelSelection, formerly activateModelAfterLogin) only record the choice in appState — used by both post-login activation and /model.

Tests: new getStartupState unit tests in test/core/harness.test.ts; TUI startup / message-flow / replay suites adapted to the deferred-creation flow. Full suite: 2365 passed (6 failures in test/cli/telemetry and test/cli/update/preflight are pre-existing on the base branch, verified by stash comparison).

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset. — no changeset per the author's call (internal migration branch, not user-facing release content).
  • Ran gen-docs skill, or this PR needs no doc update. — no user-doc impact: /new, resume and startup flags behave identically; only the eagerly-created empty session is gone.

…into tui-v2

# Conflicts:
#	packages/agent-core-v2/src/index.ts
Fresh TUI startup no longer creates an empty session. The initial render
reads config-level defaults (model, context window, permission/plan mode,
thinking effort) from the new App-scope-only CoreHarness.getStartupState(),
which resolves through IConfigService + IModelResolver without touching the
session lifecycle.

The session is created lazily on the first user message
(sendAfterLazySessionStart), with OAuth failures entering the login-required
state instead of failing startup. Model switches without a live session
(activateModelSelection, formerly activateModelAfterLogin) now only record
the choice in appState instead of creating a session.
@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 33b5ca8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 10, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@33b5ca8
npx https://pkg.pr.new/@moonshot-ai/kimi-code@33b5ca8

commit: 33b5ca8

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99fe170204

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 1099 to 1103
if (session === undefined) {
this.showError(LLM_NOT_SET_MESSAGE);
// Session-less startup: create the session lazily, then deliver this
// input as its first turn.
void this.sendAfterLazySessionStart(text, extraction);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serialize lazy startup session creation

When a fresh TUI has not created a session yet, every submitted prompt reaches this branch while this.session is still undefined; because the async sendAfterLazySessionStart() is fire-and-forget and no pending-start flag/busy state is set until after createSessionFromCurrentState() resolves, two quick Enter presses can start two independent session creations. The later one will setSession() and close/unload the earlier session while its first prompt may already be sent, dropping the first turn or leaving it in a different session. Please gate this path with a shared pending promise or mark input as busy before awaiting session creation.

Useful? React with 👍 / 👎.

The v2 engine now records `!`-command input/output with the
`shell_command` prompt origin and compaction summaries with the
`compaction_summary` origin, but the replay renderer had no branches
for them and dumped both as plain user messages (raw <bash-*> XML and
the full summary text).

Restore the v1 render paths in session-replay:
- shell_command input replays as the `$ cmd` echo (shellMode color,
  suppressed bullet); output is unwrapped from the XML tags and
  formatted through formatBashOutputForDisplay.
- compaction_summary replays as the collapsible "Compaction complete"
  card (compactionData) instead of a plain message; the
  COMPACTION_SUMMARY_PREFIX marker is stripped via a core re-export.
  Token counts stay absent until replay reads wire records (G-1).
Resume replay no longer reads the compacted context view. New module
src/core/transcript.ts declares a TranscriptModel via v2's public
defineDerivedModel and folds it manually over IAgentWireRecordService
records (the facade cannot wire.attach — replay runs inside v2 before
the facade sees the session, and replay is read once per resume).

The fold recovers the full pre-compaction history (user/assistant/tool
messages through loop-event folding), emits compaction card entries with
summary + token counts from context.apply_compaction records, and maps
goal/plan/permission/approval/config ops to their replay records. The
display-side TranscriptMessage type mirrors but does not alias
ContextMessage, so transcript content can never be fed to the model by
accident. Blob references are rehydrated through IAgentBlobService.

Entry times stay 0 (wire recordToPayload drops the envelope timestamp);
TUI renders by position, so this is safe.
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.

1 participant