mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
fix: add minor improvements for Mira (#649)
This commit is contained in:
parent
54302ad612
commit
a2c5e1be25
16 changed files with 90 additions and 38 deletions
7
.changeset/runtime-support-updates.md
Normal file
7
.changeset/runtime-support-updates.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
"@moonshot-ai/agent-core": patch
|
||||
"@moonshot-ai/kosong": patch
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Add runtime support for dynamic MCP server updates, reference skills, replay timestamps, and Node file uploads.
|
||||
|
|
@ -23,6 +23,8 @@ vi.mock('#/utils/open-url', () => ({ openUrl: vi.fn() }));
|
|||
|
||||
type GoalReplayRecord = Extract<AgentReplayRecord, { type: 'goal_updated' }>;
|
||||
|
||||
const REPLAY_TIME = 1_700_000_000_000;
|
||||
|
||||
function stripAnsi(text: string): string {
|
||||
return text.replaceAll(/\u001B\[[0-9;]*m/g, '');
|
||||
}
|
||||
|
|
@ -70,6 +72,7 @@ function message(
|
|||
} = {},
|
||||
): AgentReplayRecord {
|
||||
return {
|
||||
time: REPLAY_TIME,
|
||||
type: 'message',
|
||||
message: {
|
||||
role,
|
||||
|
|
@ -122,6 +125,7 @@ function goalReplay(
|
|||
change: GoalReplayRecord['change'],
|
||||
): GoalReplayRecord {
|
||||
return {
|
||||
time: REPLAY_TIME,
|
||||
type: 'goal_updated',
|
||||
snapshot,
|
||||
change,
|
||||
|
|
@ -988,11 +992,12 @@ describe('KimiTUI resume message replay', () => {
|
|||
|
||||
it('renders plan permission and approval replay notices', async () => {
|
||||
const driver = await replayIntoDriver([
|
||||
{ type: 'plan_updated', enabled: true },
|
||||
{ type: 'permission_updated', mode: 'auto' },
|
||||
{ type: 'permission_updated', mode: 'yolo' },
|
||||
{ type: 'permission_updated', mode: 'manual' },
|
||||
{ time: REPLAY_TIME, type: 'plan_updated', enabled: true },
|
||||
{ time: REPLAY_TIME, type: 'permission_updated', mode: 'auto' },
|
||||
{ time: REPLAY_TIME, type: 'permission_updated', mode: 'yolo' },
|
||||
{ time: REPLAY_TIME, type: 'permission_updated', mode: 'manual' },
|
||||
{
|
||||
time: REPLAY_TIME,
|
||||
type: 'approval_result',
|
||||
record: {
|
||||
turnId: 0,
|
||||
|
|
@ -1006,7 +1011,7 @@ describe('KimiTUI resume message replay', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
{ type: 'plan_updated', enabled: false },
|
||||
{ time: REPLAY_TIME, type: 'plan_updated', enabled: false },
|
||||
]);
|
||||
|
||||
const transcript = driver.state.transcriptContainer.render(120).join('\n');
|
||||
|
|
@ -1025,6 +1030,7 @@ describe('KimiTUI resume message replay', () => {
|
|||
toolCalls: [toolCall('call_exit_reject', 'ExitPlanMode', {})],
|
||||
}),
|
||||
{
|
||||
time: REPLAY_TIME,
|
||||
type: 'approval_result',
|
||||
record: {
|
||||
turnId: 0,
|
||||
|
|
@ -1042,6 +1048,7 @@ describe('KimiTUI resume message replay', () => {
|
|||
toolCalls: [toolCall('call_exit_final', 'ExitPlanMode', {})],
|
||||
}),
|
||||
{
|
||||
time: REPLAY_TIME,
|
||||
type: 'approval_result',
|
||||
record: {
|
||||
turnId: 1,
|
||||
|
|
@ -1064,7 +1071,7 @@ describe('KimiTUI resume message replay', () => {
|
|||
],
|
||||
{ toolCallId: 'call_exit_final' },
|
||||
),
|
||||
{ type: 'plan_updated', enabled: false },
|
||||
{ time: REPLAY_TIME, type: 'plan_updated', enabled: false },
|
||||
]);
|
||||
|
||||
const transcript = driver.state.transcriptContainer.render(120).join('\n');
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
migrateWireRecord,
|
||||
resolveWireMigrations,
|
||||
type WireMigration,
|
||||
} from '@moonshot-ai/agent-core/agent/records/migration';
|
||||
} from '@moonshot-ai/agent-core/agent/records/migration/index';
|
||||
|
||||
import type { AgentRecord, WireEntry } from './agent-record-types';
|
||||
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@
|
|||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
},
|
||||
"./agent/records/migration": {
|
||||
"types": "./src/agent/records/migration/index.ts",
|
||||
"default": "./src/agent/records/migration/index.ts"
|
||||
"./package.json": {
|
||||
"types": "./package.json",
|
||||
"default": "./package.json"
|
||||
},
|
||||
"./session/store": {
|
||||
"types": "./src/session/store/index.ts",
|
||||
"default": "./src/session/store/index.ts"
|
||||
"./*": {
|
||||
"types": "./src/*.ts",
|
||||
"default": "./src/*.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ import type { PermissionPolicy, PermissionPolicyContext, PermissionPolicyResult
|
|||
export class SensitiveFileAccessAskPermissionPolicy implements PermissionPolicy {
|
||||
readonly name = 'sensitive-file-access-ask';
|
||||
|
||||
constructor(private readonly agent: Agent) {}
|
||||
|
||||
evaluate(context: PermissionPolicyContext): PermissionPolicyResult | undefined {
|
||||
const access = fileAccesses(context).find((fileAccess) =>
|
||||
isSensitiveFile(fileAccess.path),
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export function createPermissionDecisionPolicies(agent: Agent): PermissionPolicy
|
|||
// EnterPlanMode, Write/Edit on the plan file, or ExitPlanMode with no actionable plan_review → approve.
|
||||
new PlanModeToolApprovePermissionPolicy(agent),
|
||||
// Access touches a sensitive file (.env, SSH key, credentials) → ask.
|
||||
new SensitiveFileAccessAskPermissionPolicy(agent),
|
||||
new SensitiveFileAccessAskPermissionPolicy(),
|
||||
// Access touches .git or a git control-dir path → ask.
|
||||
new GitControlPathAccessAskPermissionPolicy(agent),
|
||||
// yolo mode → approve.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import type { Agent } from '..';
|
||||
import type { AgentReplayRecord } from '../..';
|
||||
import type { AgentReplayRecord, AgentReplayRecordPayload } from '../..';
|
||||
import type { ContextMessage } from '../context';
|
||||
|
||||
export class ReplayBuilder {
|
||||
captureLiveRecords = false;
|
||||
protected readonly records: AgentReplayRecord[] = [];
|
||||
|
||||
constructor(public readonly agent: Agent) {}
|
||||
|
||||
push(record: AgentReplayRecord): void {
|
||||
if (this.agent.records.restoring) {
|
||||
this.records.push(record);
|
||||
push(record: AgentReplayRecordPayload): void {
|
||||
if (this.captureLiveRecords || this.agent.records.restoring) {
|
||||
this.records.push({
|
||||
...record,
|
||||
time: this.agent.records.restoring?.time ?? Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ export class RotatingFileSink implements Sink {
|
|||
private pending: string[] = [];
|
||||
private dropped = 0;
|
||||
private closed = false;
|
||||
private failed = false;
|
||||
private lastStderrNotice = 0;
|
||||
private currentBytes = -1;
|
||||
private directorySynced = false;
|
||||
|
|
@ -103,7 +102,6 @@ export class RotatingFileSink implements Sink {
|
|||
this.directorySynced = true;
|
||||
}
|
||||
|
||||
this.failed = false;
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.noteFailure(error);
|
||||
|
|
@ -212,7 +210,6 @@ export class RotatingFileSink implements Sink {
|
|||
}
|
||||
|
||||
private noteFailure(error: unknown): void {
|
||||
this.failed = true;
|
||||
const now = Date.now();
|
||||
if (now - this.lastStderrNotice < STDERR_NOTICE_INTERVAL_MS) return;
|
||||
this.lastStderrNotice = now;
|
||||
|
|
|
|||
|
|
@ -155,6 +155,38 @@ export class McpConnectionManager {
|
|||
return initialLoad;
|
||||
}
|
||||
|
||||
async connect(name: string, config: McpServerConfig): Promise<void> {
|
||||
const previous = this.entries.get(name);
|
||||
if (previous !== undefined) {
|
||||
await this.closeClient(previous);
|
||||
}
|
||||
const disabled = config.enabled === false;
|
||||
const entry: InternalEntry = {
|
||||
name,
|
||||
config,
|
||||
attemptId: 0,
|
||||
status: disabled ? 'disabled' : 'pending',
|
||||
};
|
||||
this.entries.set(name, entry);
|
||||
this.emit(entry);
|
||||
if (!disabled) {
|
||||
await this.connectOne(entry, this.beginConnectAttempt(entry));
|
||||
}
|
||||
}
|
||||
|
||||
async remove(name: string): Promise<boolean> {
|
||||
const entry = this.entries.get(name);
|
||||
if (entry === undefined) return false;
|
||||
await this.closeClient(entry);
|
||||
entry.status = 'disabled';
|
||||
entry.tools = undefined;
|
||||
entry.enabledNames = undefined;
|
||||
entry.error = undefined;
|
||||
this.emit(entry);
|
||||
this.entries.delete(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
waitForInitialLoad(signal?: AbortSignal): Promise<void> {
|
||||
signal?.throwIfAborted();
|
||||
if (signal === undefined) return this.initialLoad;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import {
|
|||
type OAuthTokenProviderResolver
|
||||
} from '../session/provider-manager';
|
||||
import { SessionAPIImpl } from '../session/rpc';
|
||||
import { normalizeWorkDir, SessionStore } from '../session/store';
|
||||
import { normalizeWorkDir, SessionStore } from '../session/store/index';
|
||||
import { noopTelemetryClient, withTelemetryContext, type TelemetryClient } from '../telemetry';
|
||||
import type { CoreRPCClient } from './client';
|
||||
import type {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import type { SessionSummary } from '#/rpc/core-api';
|
|||
import type { UsageStatus } from '#/rpc/events';
|
||||
import type { SessionMeta } from '#/session';
|
||||
|
||||
export type AgentReplayRecord =
|
||||
export type AgentReplayRecordPayload =
|
||||
| { type: 'message'; message: ContextMessage }
|
||||
| {
|
||||
type: 'goal_updated';
|
||||
|
|
@ -26,6 +26,8 @@ export type AgentReplayRecord =
|
|||
| { type: 'permission_updated'; mode: PermissionMode }
|
||||
| { type: 'approval_result'; record: PermissionApprovalResultRecord };
|
||||
|
||||
export type AgentReplayRecord = { readonly time: number } & AgentReplayRecordPayload;
|
||||
|
||||
export interface ResumedAgentState {
|
||||
readonly type: AgentType;
|
||||
readonly config: AgentConfigData;
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export function isUserActivatableSkillType(type: string | undefined): boolean {
|
|||
}
|
||||
|
||||
export function isSupportedSkillType(type: string | undefined): boolean {
|
||||
return isUserActivatableSkillType(type);
|
||||
return isUserActivatableSkillType(type) || type === 'reference';
|
||||
}
|
||||
|
||||
export function summarizeSkill(skill: SkillDefinition): SkillSummary {
|
||||
|
|
|
|||
|
|
@ -2463,10 +2463,12 @@ describe('Agent-local approve for session', () => {
|
|||
|
||||
ctx.dispatch(event);
|
||||
|
||||
expect(ctx.agent.replayBuilder.buildResult()).toContainEqual({
|
||||
type: 'approval_result',
|
||||
record: event,
|
||||
});
|
||||
expect(ctx.agent.replayBuilder.buildResult()).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: 'approval_result',
|
||||
record: event,
|
||||
}),
|
||||
);
|
||||
expect(ctx.agent.permission.data().rules).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -313,14 +313,16 @@ describe('Agent resume', () => {
|
|||
|
||||
await ctx.agent.resume();
|
||||
|
||||
expect(ctx.agent.replayBuilder.buildResult()).toContainEqual({
|
||||
type: 'message',
|
||||
message: expect.objectContaining({
|
||||
role: 'tool',
|
||||
toolCallId: 'call_failed_bash',
|
||||
isError: true,
|
||||
expect(ctx.agent.replayBuilder.buildResult()).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: 'message',
|
||||
message: expect.objectContaining({
|
||||
role: 'tool',
|
||||
toolCallId: 'call_failed_bash',
|
||||
isError: true,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
it('rebuilds goal completion replay cards without adding model-visible context', async () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { Blob, File } from 'node:buffer';
|
||||
|
||||
import { ChatProviderError } from '#/errors';
|
||||
import type { VideoURLPart } from '#/message';
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
SessionStore,
|
||||
encodeWorkDirKey,
|
||||
normalizeWorkDir,
|
||||
} from '@moonshot-ai/agent-core/session/store';
|
||||
} from '@moonshot-ai/agent-core/session/store/index';
|
||||
import { Session, type SDKSessionRPC } from '@moonshot-ai/agent-core';
|
||||
import { LocalKaos } from '@moonshot-ai/kaos';
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue