-
Notifications
You must be signed in to change notification settings - Fork 916
feat: add reload commands #383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5f7bc2d
51c70c5
eb805e2
aa19d63
c99b230
29519a4
ed23fe0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@moonshot-ai/agent-core": minor | ||
| "@moonshot-ai/kimi-code-sdk": minor | ||
| "@moonshot-ai/kimi-code": minor | ||
| --- | ||
|
|
||
| Add /reload to reload the current session and apply updated config files, plus /reload-tui to reload only TUI preferences. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import type { KimiConfig } from '@moonshot-ai/kimi-code-sdk'; | ||
|
|
||
| import { loadTuiConfig, type TuiConfig } from '../config'; | ||
| import type { SlashCommandHost } from './dispatch'; | ||
|
|
||
| export async function handleReloadTuiCommand(host: SlashCommandHost): Promise<void> { | ||
| const tuiConfig = await loadTuiConfig(); | ||
| applyReloadedTuiConfig(host, tuiConfig); | ||
| host.showStatus('TUI config reloaded.', host.state.theme.colors.success); | ||
| } | ||
|
|
||
| export async function handleReloadCommand(host: SlashCommandHost): Promise<void> { | ||
| const tuiConfig = await loadTuiConfig(); | ||
| const session = host.session; | ||
|
|
||
| if (session !== undefined) { | ||
| await session.reloadSession(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because slash-command execution is fire-and-forget and this active-session path does not call Useful? React with 👍 / 👎. |
||
| await host.reloadCurrentSessionView(session, 'Session reloaded.'); | ||
| } | ||
|
|
||
| const config = await host.harness.getConfig({ reload: true }); | ||
| applyRuntimeConfig(host, config); | ||
|
liruifengv marked this conversation as resolved.
|
||
| applyReloadedTuiConfig(host, tuiConfig); | ||
|
|
||
| if (session === undefined) { | ||
| host.showStatus( | ||
| 'Runtime and TUI config reloaded; no active session.', | ||
| host.state.theme.colors.success, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export function applyReloadedTuiConfig( | ||
| host: SlashCommandHost, | ||
| config: TuiConfig, | ||
| ): void { | ||
| const resolved = config.theme === 'auto' ? host.state.theme.resolvedTheme : config.theme; | ||
| host.applyTheme(config.theme, resolved); | ||
| host.refreshTerminalThemeTracking(); | ||
| host.setAppState({ | ||
| editorCommand: config.editorCommand, | ||
| notifications: config.notifications, | ||
| upgrade: config.upgrade, | ||
| }); | ||
| } | ||
|
|
||
| function applyRuntimeConfig(host: SlashCommandHost, config: KimiConfig): void { | ||
| host.setAppState({ | ||
| availableModels: config.models ?? {}, | ||
| availableProviders: config.providers ?? {}, | ||
|
liruifengv marked this conversation as resolved.
|
||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| import { mkdir, rm, writeFile } from 'node:fs/promises'; | ||
| import { tmpdir } from 'node:os'; | ||
| import { join } from 'node:path'; | ||
|
|
||
| import { afterEach, describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| import { | ||
| handleReloadCommand, | ||
| handleReloadTuiCommand, | ||
| } from '#/tui/commands/reload'; | ||
| import type { SlashCommandHost } from '#/tui/commands'; | ||
|
|
||
| const tempDirs: string[] = []; | ||
| const originalKimiCodeHome = process.env['KIMI_CODE_HOME']; | ||
|
|
||
| afterEach(async () => { | ||
| for (const dir of tempDirs.splice(0)) { | ||
| await rm(dir, { recursive: true, force: true }); | ||
| } | ||
| if (originalKimiCodeHome === undefined) { | ||
| delete process.env['KIMI_CODE_HOME']; | ||
| } else { | ||
| process.env['KIMI_CODE_HOME'] = originalKimiCodeHome; | ||
| } | ||
| }); | ||
|
|
||
| describe('reload slash commands', () => { | ||
| it('reloads tui.toml without touching Core session state', async () => { | ||
| await writeTuiConfig(` | ||
| theme = "light" | ||
|
|
||
| [editor] | ||
| command = "vim" | ||
|
|
||
| [notifications] | ||
| enabled = false | ||
| notification_condition = "always" | ||
|
|
||
| [upgrade] | ||
| auto_install = false | ||
| `); | ||
| const session = { reloadSession: vi.fn() }; | ||
| const host = makeHost({ session }); | ||
|
|
||
| await handleReloadTuiCommand(host); | ||
|
|
||
| expect(host.harness.getConfig).not.toHaveBeenCalled(); | ||
| expect(session.reloadSession).not.toHaveBeenCalled(); | ||
| expect(host.state.appState).toMatchObject({ | ||
| theme: 'light', | ||
| editorCommand: 'vim', | ||
| notifications: { enabled: false, condition: 'always' }, | ||
| upgrade: { autoInstall: false }, | ||
| }); | ||
| expect(host.showStatus).toHaveBeenCalledWith( | ||
| 'TUI config reloaded.', | ||
| host.state.theme.colors.success, | ||
| ); | ||
| }); | ||
|
|
||
| it('reloads the active session, refreshes runtime config, and applies tui.toml', async () => { | ||
| await writeTuiConfig('theme = "light"\n'); | ||
| const session = { id: 'ses-1', reloadSession: vi.fn(async () => ({})) }; | ||
| const host = makeHost({ session }); | ||
|
|
||
| await handleReloadCommand(host); | ||
|
|
||
| expect(session.reloadSession).toHaveBeenCalledOnce(); | ||
| expect(host.reloadCurrentSessionView).toHaveBeenCalledWith( | ||
| session, | ||
| 'Session reloaded.', | ||
| ); | ||
| expect(host.harness.getConfig).toHaveBeenCalledWith({ reload: true }); | ||
| expect(host.state.appState.theme).toBe('light'); | ||
| expect(host.state.appState.availableModels).toEqual({ | ||
| fresh: { provider: 'test', model: 'fresh-model', maxContextSize: 1000 }, | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| async function writeTuiConfig(text: string): Promise<void> { | ||
| const dir = join(tmpdir(), `kimi-tui-reload-${Date.now()}-${Math.random().toString(36).slice(2)}`); | ||
| tempDirs.push(dir); | ||
| await mkdir(dir, { recursive: true }); | ||
| process.env['KIMI_CODE_HOME'] = dir; | ||
| await writeFile(join(dir, 'tui.toml'), text, 'utf-8'); | ||
| } | ||
|
|
||
| function makeHost({ | ||
| session, | ||
| }: { | ||
| readonly session?: Record<string, unknown>; | ||
| } = {}) { | ||
| const state = { | ||
| appState: { | ||
| theme: 'dark', | ||
| editorCommand: null, | ||
| notifications: { enabled: true, condition: 'unfocused' }, | ||
| upgrade: { autoInstall: true }, | ||
| availableModels: {}, | ||
| availableProviders: {}, | ||
| }, | ||
| theme: { | ||
| resolvedTheme: 'dark', | ||
| colors: { | ||
| success: '#00ff00', | ||
| }, | ||
| }, | ||
| }; | ||
| return { | ||
| state, | ||
| session, | ||
| harness: { | ||
| getConfig: vi.fn(async () => ({ | ||
| models: { | ||
| fresh: { provider: 'test', model: 'fresh-model', maxContextSize: 1000 }, | ||
| }, | ||
| providers: { | ||
| test: { type: 'kimi', apiKey: 'test-key' }, | ||
| }, | ||
| })), | ||
| }, | ||
| setAppState: vi.fn((patch: Record<string, unknown>) => { | ||
| Object.assign(state.appState, patch); | ||
| }), | ||
| applyTheme: vi.fn((theme: string) => { | ||
| state.appState.theme = theme; | ||
| }), | ||
| refreshTerminalThemeTracking: vi.fn(), | ||
| reloadCurrentSessionView: vi.fn(async () => {}), | ||
| showStatus: vi.fn(), | ||
| } as unknown as SlashCommandHost & { | ||
| readonly harness: { | ||
| readonly getConfig: ReturnType<typeof vi.fn>; | ||
| }; | ||
| readonly reloadCurrentSessionView: ReturnType<typeof vi.fn>; | ||
| readonly showStatus: ReturnType<typeof vi.fn>; | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
tui.tomlhas a parse or schema error,loadTuiConfig()throwsTuiConfigParseError; startup catches that error and applies its fallback defaults, but this new/reloadpath does not. In that scenario/reloadaborts before reloading the active session or runtime config at all, even though the error message says defaults are being used, so a bad UI preference file prevents users from applying fixedconfig.tomlchanges until they restart or manually fix the TUI file.Useful? React with 👍 / 👎.