feat(tui): run interactive shell on agent-core-v2 with deferred session creation#1543
feat(tui): run interactive shell on agent-core-v2 with deferred session creation#1543liruifengv wants to merge 5 commits into
Conversation
…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.
|
commit: |
There was a problem hiding this comment.
💡 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".
| 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; |
There was a problem hiding this comment.
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.
Related Issue
No related issue — this lands the
tui-v2work branch ontokimi-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 ontoagent-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:
250bd460feat(tui): migrate interactive shell to agent-core-v2apps/kimi-code/src/core/(CoreHarnessat App scope,CoreSessionat session scope); the interactive TUI reaches the engine only through this facade. Print (kimi -p), ACP and other subcommands still consume the v1 SDK.TODO(v2-gap), documented insrc/core/README.md.99fe1702feat(tui): defer session creation to the first message on fresh startupCoreHarness.getStartupState()(IConfigService+IModelResolver, plus the profile service's own thinking resolution).sendAfterLazySessionStart); OAuth login-required errors now surface at first message instead of failing startup.activateModelSelection, formerlyactivateModelAfterLogin) only record the choice in appState — used by both post-login activation and/model.Tests: new
getStartupStateunit tests intest/core/harness.test.ts; TUI startup / message-flow / replay suites adapted to the deferred-creation flow. Full suite: 2365 passed (6 failures intest/cli/telemetryandtest/cli/update/preflightare pre-existing on the base branch, verified by stash comparison).Checklist
gen-changesetsskill, or this PR needs no changeset. — no changeset per the author's call (internal migration branch, not user-facing release content).gen-docsskill, 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.