mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
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
This commit is contained in:
parent
55870616ca
commit
d599183c8e
8 changed files with 72 additions and 7 deletions
7
.changeset/export-install-source-shell-env.md
Normal file
7
.changeset/export-install-source-shell-env.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
"@moonshot-ai/agent-core": patch
|
||||
"@moonshot-ai/kimi-code-sdk": patch
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Enhance `kimi export` to include more diagnostic information in the manifest.
|
||||
|
|
@ -18,12 +18,14 @@ import {
|
|||
type ExportSessionInput,
|
||||
type ExportSessionResult,
|
||||
type SessionSummary,
|
||||
type ShellEnvironment,
|
||||
type TelemetryClient,
|
||||
} from '@moonshot-ai/kimi-code-sdk';
|
||||
import type { Command } from 'commander';
|
||||
|
||||
import { CLI_SHUTDOWN_TIMEOUT_MS, CLI_UI_MODE } from '#/constant/app';
|
||||
import { createCliTelemetryBootstrap, initializeCliTelemetry } from '#/cli/telemetry';
|
||||
import { detectInstallSource } from '#/cli/update/source';
|
||||
import { createKimiCodeHostIdentity } from '#/cli/version';
|
||||
|
||||
interface WritableLike {
|
||||
|
|
@ -41,6 +43,8 @@ export interface ExportDeps {
|
|||
readonly listSessions: (workDir: string) => Promise<readonly SessionSummary[]>;
|
||||
readonly exportSession: (input: ExportSessionInput) => Promise<ExportSessionResult>;
|
||||
readonly confirmPreviousSession: (summary: PreviousSessionSummary) => Promise<boolean>;
|
||||
readonly getInstallSource: () => Promise<string>;
|
||||
readonly getShellEnv: () => ShellEnvironment;
|
||||
readonly version: string;
|
||||
readonly cwd: () => string;
|
||||
readonly stdout: WritableLike;
|
||||
|
|
@ -81,9 +85,13 @@ export async function handleExport(
|
|||
}
|
||||
|
||||
try {
|
||||
const installSource = await deps.getInstallSource();
|
||||
const shellEnv = deps.getShellEnv();
|
||||
const result = await deps.exportSession({
|
||||
id: resolvedId,
|
||||
version: deps.version,
|
||||
installSource,
|
||||
shellEnv,
|
||||
...(output === undefined ? {} : { outputPath: output }),
|
||||
...(opts.includeGlobalLog ? { includeGlobalLog: true } : {}),
|
||||
});
|
||||
|
|
@ -180,6 +188,8 @@ function createDefaultExportDeps(overrides: Partial<ExportDeps> = {}): ExportDep
|
|||
}
|
||||
}),
|
||||
version: overrides.version ?? identity.version,
|
||||
getInstallSource: overrides.getInstallSource ?? (() => detectInstallSource()),
|
||||
getShellEnv: overrides.getShellEnv ?? detectShellEnvironment,
|
||||
confirmPreviousSession: overrides.confirmPreviousSession ?? confirmPreviousSession,
|
||||
cwd: overrides.cwd ?? (() => process.cwd()),
|
||||
stdout: overrides.stdout ?? process.stdout,
|
||||
|
|
@ -221,6 +231,23 @@ async function confirmPreviousSession(summary: PreviousSessionSummary): Promise<
|
|||
}
|
||||
}
|
||||
|
||||
function detectMultiplexer(): string | undefined {
|
||||
if (process.env['TMUX']) return 'tmux';
|
||||
if (process.env['STY']) return 'screen';
|
||||
if (process.env['ZELLIJ']) return 'zellij';
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function detectShellEnvironment(): ShellEnvironment {
|
||||
return {
|
||||
term: process.env['TERM'] || undefined,
|
||||
termProgram: process.env['TERM_PROGRAM'] || undefined,
|
||||
termProgramVersion: process.env['TERM_PROGRAM_VERSION'] || undefined,
|
||||
multiplexer: detectMultiplexer(),
|
||||
shell: process.env['SHELL'] || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,8 @@ function makeDeps(overrides: Partial<ExportDeps> = {}): {
|
|||
return makeResult(input.id, input.outputPath ?? join(tmp, `${input.id}.zip`));
|
||||
},
|
||||
confirmPreviousSession: async () => true,
|
||||
getInstallSource: async () => 'npm-global',
|
||||
getShellEnv: () => ({ term: 'xterm-256color', shell: '/bin/zsh' }),
|
||||
version: '1.0.0-test',
|
||||
cwd: () => tmp,
|
||||
stdout: {
|
||||
|
|
@ -223,7 +225,7 @@ describe('kimi export', () => {
|
|||
expect(exitCodes).toEqual([]);
|
||||
expect(stderr).toEqual([]);
|
||||
expect(listedWorkDirs).toEqual([]);
|
||||
expect(exportInputs).toEqual([{ id: 'ses_test123456', outputPath: output, includeGlobalLog: true, version: '1.0.0-test' }]);
|
||||
expect(exportInputs).toEqual([{ id: 'ses_test123456', outputPath: output, includeGlobalLog: true, version: '1.0.0-test', installSource: 'npm-global', shellEnv: { term: 'xterm-256color', shell: '/bin/zsh' } }]);
|
||||
expect(stdout.join('').trim()).toBe(output);
|
||||
});
|
||||
|
||||
|
|
@ -232,7 +234,7 @@ describe('kimi export', () => {
|
|||
|
||||
await runExport(deps, { sessionId: 'session_default_output' });
|
||||
|
||||
expect(exportInputs).toEqual([{ id: 'session_default_output', includeGlobalLog: true, version: '1.0.0-test' }]);
|
||||
expect(exportInputs).toEqual([{ id: 'session_default_output', includeGlobalLog: true, version: '1.0.0-test', installSource: 'npm-global', shellEnv: { term: 'xterm-256color', shell: '/bin/zsh' } }]);
|
||||
expect(stdout.join('').trim()).toBe(join(tmp, 'session_default_output.zip'));
|
||||
});
|
||||
|
||||
|
|
@ -270,7 +272,7 @@ describe('kimi export', () => {
|
|||
await runExport(deps, { output });
|
||||
|
||||
expect(exitCodes).toEqual([]);
|
||||
expect(exportInputs).toEqual([{ id: 'ses_fallback', outputPath: output, includeGlobalLog: true, version: '1.0.0-test' }]);
|
||||
expect(exportInputs).toEqual([{ id: 'ses_fallback', outputPath: output, includeGlobalLog: true, version: '1.0.0-test', installSource: 'npm-global', shellEnv: { term: 'xterm-256color', shell: '/bin/zsh' } }]);
|
||||
expect(stdout.join('').trim()).toBe(output);
|
||||
});
|
||||
|
||||
|
|
@ -312,7 +314,7 @@ describe('kimi export', () => {
|
|||
await runExport(deps, { output: join(tmp, 'yes.zip'), yes: true });
|
||||
|
||||
expect(exitCodes).toEqual([]);
|
||||
expect(exportInputs).toEqual([{ id: 'ses_yes', outputPath: join(tmp, 'yes.zip'), includeGlobalLog: true, version: '1.0.0-test' }]);
|
||||
expect(exportInputs).toEqual([{ id: 'ses_yes', outputPath: join(tmp, 'yes.zip'), includeGlobalLog: true, version: '1.0.0-test', installSource: 'npm-global', shellEnv: { term: 'xterm-256color', shell: '/bin/zsh' } }]);
|
||||
});
|
||||
|
||||
it('describes the user-facing command without implementation details', () => {
|
||||
|
|
@ -338,7 +340,7 @@ describe('kimi export', () => {
|
|||
await program.parseAsync(['node', 'kimi', 'export', '--no-include-global-log', '-y']);
|
||||
|
||||
expect(exitCodes).toEqual([]);
|
||||
expect(exportInputs).toEqual([{ id: 'ses_global_log', version: '1.0.0-test' }]);
|
||||
expect(exportInputs).toEqual([{ id: 'ses_global_log', version: '1.0.0-test', installSource: 'npm-global', shellEnv: { term: 'xterm-256color', shell: '/bin/zsh' } }]);
|
||||
expect(stdout.join('').trim()).toBe(join(tmp, 'ses_global_log.zip'));
|
||||
});
|
||||
|
||||
|
|
@ -361,7 +363,7 @@ describe('kimi export', () => {
|
|||
|
||||
expect(exitCodes).toEqual([]);
|
||||
expect(exportInputs).toEqual([
|
||||
{ id: 'ses_after_id', outputPath: output, version: '1.0.0-test' },
|
||||
{ id: 'ses_after_id', outputPath: output, version: '1.0.0-test', installSource: 'npm-global', shellEnv: { term: 'xterm-256color', shell: '/bin/zsh' } },
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -420,6 +422,8 @@ describe('kimi export', () => {
|
|||
outputPath: output,
|
||||
version: expect.any(String),
|
||||
includeGlobalLog: true,
|
||||
installSource: expect.any(String),
|
||||
shellEnv: expect.objectContaining({ shell: expect.any(String) }),
|
||||
});
|
||||
expect(mocks.shutdownTelemetry).toHaveBeenCalledWith({ timeoutMs: 3000 });
|
||||
expect(mocks.harnessExportSession.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
|
|
|
|||
|
|
@ -52,6 +52,14 @@ export interface ForkSessionPayload {
|
|||
readonly metadata?: JsonObject;
|
||||
}
|
||||
|
||||
export interface ShellEnvironment {
|
||||
readonly term?: string | undefined;
|
||||
readonly termProgram?: string | undefined;
|
||||
readonly termProgramVersion?: string | undefined;
|
||||
readonly multiplexer?: string | undefined;
|
||||
readonly shell?: string | undefined;
|
||||
}
|
||||
|
||||
export interface ExportSessionPayload {
|
||||
readonly sessionId: string;
|
||||
readonly outputPath?: string | undefined;
|
||||
|
|
@ -63,6 +71,9 @@ export interface ExportSessionPayload {
|
|||
readonly includeGlobalLog?: boolean | undefined;
|
||||
/** Host version to record in the export manifest. */
|
||||
readonly version: string;
|
||||
/** How the CLI was installed (e.g. 'npm-global', 'native'). */
|
||||
readonly installSource?: string | undefined;
|
||||
readonly shellEnv?: ShellEnvironment | undefined;
|
||||
}
|
||||
|
||||
export interface ExportSessionManifest {
|
||||
|
|
@ -80,6 +91,9 @@ export interface ExportSessionManifest {
|
|||
readonly sessionLogPath?: string | undefined;
|
||||
/** zip-relative path to the bundled global diagnostic log (only when --include-global-log). */
|
||||
readonly globalLogPath?: string | undefined;
|
||||
/** How the CLI was installed (e.g. 'npm-global', 'native'). */
|
||||
readonly installSource?: string | undefined;
|
||||
readonly shellEnv?: ShellEnvironment | undefined;
|
||||
}
|
||||
|
||||
export interface ExportSessionResult {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { AGENT_WIRE_PROTOCOL_VERSION } from '../../agent/records';
|
||||
import type { SessionWireScan } from '#/session/export/wire-scan';
|
||||
import type { ExportSessionManifest, SessionSummary } from '#/rpc/core-api';
|
||||
import type { ExportSessionManifest, ShellEnvironment, SessionSummary } from '#/rpc/core-api';
|
||||
|
||||
export const WIRE_PROTOCOL_VERSION = AGENT_WIRE_PROTOCOL_VERSION;
|
||||
|
||||
|
|
@ -12,6 +12,8 @@ export function buildExportManifest(args: {
|
|||
readonly sessionScan: SessionWireScan;
|
||||
readonly sessionLogPath?: string | undefined;
|
||||
readonly globalLogPath?: string | undefined;
|
||||
readonly installSource?: string | undefined;
|
||||
readonly shellEnv?: ShellEnvironment | undefined;
|
||||
}): ExportSessionManifest {
|
||||
return {
|
||||
sessionId: args.summary.id,
|
||||
|
|
@ -32,5 +34,7 @@ export function buildExportManifest(args: {
|
|||
workspaceDir: args.summary.workDir,
|
||||
sessionLogPath: args.sessionLogPath,
|
||||
globalLogPath: args.globalLogPath,
|
||||
installSource: args.installSource,
|
||||
shellEnv: args.shellEnv,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ export async function exportSessionDirectory(input: {
|
|||
sessionScan,
|
||||
sessionLogPath: hasSessionLog ? SESSION_LOG_REL : undefined,
|
||||
globalLogPath: bundledGlobal ? GLOBAL_LOG_REL : undefined,
|
||||
installSource: input.request.installSource,
|
||||
shellEnv: input.request.shellEnv,
|
||||
});
|
||||
|
||||
const outputPath =
|
||||
|
|
|
|||
|
|
@ -183,6 +183,8 @@ export class SDKRpcClient {
|
|||
outputPath: input.outputPath,
|
||||
includeGlobalLog: input.includeGlobalLog,
|
||||
version: input.version,
|
||||
installSource: input.installSource,
|
||||
shellEnv: input.shellEnv,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type {
|
||||
ExportSessionManifest,
|
||||
ResumeSessionResult,
|
||||
ShellEnvironment,
|
||||
TelemetryClient,
|
||||
TelemetryContextPatch,
|
||||
TelemetryProperties,
|
||||
|
|
@ -35,6 +36,7 @@ export type {
|
|||
ProviderType,
|
||||
ResumedAgentState,
|
||||
ServicesConfig,
|
||||
ShellEnvironment,
|
||||
SkillSummary,
|
||||
ThinkingConfig,
|
||||
ToolInfo,
|
||||
|
|
@ -94,6 +96,9 @@ export interface ExportSessionInput {
|
|||
readonly includeGlobalLog?: boolean | undefined;
|
||||
/** Host version to record in the export manifest. */
|
||||
readonly version: string;
|
||||
/** How the CLI was installed (e.g. 'npm-global', 'native'). */
|
||||
readonly installSource?: string | undefined;
|
||||
readonly shellEnv?: ShellEnvironment | undefined;
|
||||
}
|
||||
|
||||
export interface ExportSessionResult {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue