From a2c5e1be25484f7c52f729e333196c485f83b84c Mon Sep 17 00:00:00 2001 From: _Kerman Date: Thu, 11 Jun 2026 15:50:33 +0800 Subject: [PATCH] fix: add minor improvements for Mira (#649) --- .changeset/runtime-support-updates.md | 7 ++++ .../kimi-code/test/tui/message-replay.test.ts | 19 +++++++---- apps/vis/server/src/lib/wire-reader.ts | 2 +- packages/agent-core/package.json | 12 +++---- .../permission/policies/file-access-ask.ts | 2 -- .../src/agent/permission/policies/index.ts | 2 +- packages/agent-core/src/agent/replay/index.ts | 12 ++++--- packages/agent-core/src/logging/sinks.ts | 3 -- .../agent-core/src/mcp/connection-manager.ts | 32 +++++++++++++++++++ packages/agent-core/src/rpc/core-impl.ts | 2 +- packages/agent-core/src/rpc/resumed.ts | 4 ++- packages/agent-core/src/skill/types.ts | 2 +- .../agent-core/test/agent/permission.test.ts | 10 +++--- packages/agent-core/test/agent/resume.test.ts | 16 ++++++---- packages/kosong/src/providers/kimi-files.ts | 1 + .../test/resume.integration.test.ts | 2 +- 16 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 .changeset/runtime-support-updates.md diff --git a/.changeset/runtime-support-updates.md b/.changeset/runtime-support-updates.md new file mode 100644 index 000000000..050bed3d8 --- /dev/null +++ b/.changeset/runtime-support-updates.md @@ -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. diff --git a/apps/kimi-code/test/tui/message-replay.test.ts b/apps/kimi-code/test/tui/message-replay.test.ts index c7289a0ba..8a02b505b 100644 --- a/apps/kimi-code/test/tui/message-replay.test.ts +++ b/apps/kimi-code/test/tui/message-replay.test.ts @@ -23,6 +23,8 @@ vi.mock('#/utils/open-url', () => ({ openUrl: vi.fn() })); type GoalReplayRecord = Extract; +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'); diff --git a/apps/vis/server/src/lib/wire-reader.ts b/apps/vis/server/src/lib/wire-reader.ts index b077e1773..67ee4cd20 100644 --- a/apps/vis/server/src/lib/wire-reader.ts +++ b/apps/vis/server/src/lib/wire-reader.ts @@ -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'; diff --git a/packages/agent-core/package.json b/packages/agent-core/package.json index a74f15af6..a6649cec8 100644 --- a/packages/agent-core/package.json +++ b/packages/agent-core/package.json @@ -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": { diff --git a/packages/agent-core/src/agent/permission/policies/file-access-ask.ts b/packages/agent-core/src/agent/permission/policies/file-access-ask.ts index f71d4b4b1..8c1105062 100644 --- a/packages/agent-core/src/agent/permission/policies/file-access-ask.ts +++ b/packages/agent-core/src/agent/permission/policies/file-access-ask.ts @@ -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), diff --git a/packages/agent-core/src/agent/permission/policies/index.ts b/packages/agent-core/src/agent/permission/policies/index.ts index 334fe0658..291d0f1b3 100644 --- a/packages/agent-core/src/agent/permission/policies/index.ts +++ b/packages/agent-core/src/agent/permission/policies/index.ts @@ -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. diff --git a/packages/agent-core/src/agent/replay/index.ts b/packages/agent-core/src/agent/replay/index.ts index acb760055..c5f851869 100644 --- a/packages/agent-core/src/agent/replay/index.ts +++ b/packages/agent-core/src/agent/replay/index.ts @@ -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(), + }); } } diff --git a/packages/agent-core/src/logging/sinks.ts b/packages/agent-core/src/logging/sinks.ts index e7ceb23be..ef23eb101 100644 --- a/packages/agent-core/src/logging/sinks.ts +++ b/packages/agent-core/src/logging/sinks.ts @@ -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; diff --git a/packages/agent-core/src/mcp/connection-manager.ts b/packages/agent-core/src/mcp/connection-manager.ts index fb3b861f2..30f269e1c 100644 --- a/packages/agent-core/src/mcp/connection-manager.ts +++ b/packages/agent-core/src/mcp/connection-manager.ts @@ -155,6 +155,38 @@ export class McpConnectionManager { return initialLoad; } + async connect(name: string, config: McpServerConfig): Promise { + 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 { + 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 { signal?.throwIfAborted(); if (signal === undefined) return this.initialLoad; diff --git a/packages/agent-core/src/rpc/core-impl.ts b/packages/agent-core/src/rpc/core-impl.ts index 86db94e96..c8edc631d 100644 --- a/packages/agent-core/src/rpc/core-impl.ts +++ b/packages/agent-core/src/rpc/core-impl.ts @@ -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 { diff --git a/packages/agent-core/src/rpc/resumed.ts b/packages/agent-core/src/rpc/resumed.ts index 9f3e3b129..4fc635331 100644 --- a/packages/agent-core/src/rpc/resumed.ts +++ b/packages/agent-core/src/rpc/resumed.ts @@ -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; diff --git a/packages/agent-core/src/skill/types.ts b/packages/agent-core/src/skill/types.ts index 21a44db60..e7485fc16 100644 --- a/packages/agent-core/src/skill/types.ts +++ b/packages/agent-core/src/skill/types.ts @@ -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 { diff --git a/packages/agent-core/test/agent/permission.test.ts b/packages/agent-core/test/agent/permission.test.ts index a57200c68..21460b5fc 100644 --- a/packages/agent-core/test/agent/permission.test.ts +++ b/packages/agent-core/test/agent/permission.test.ts @@ -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([]); }); }); diff --git a/packages/agent-core/test/agent/resume.test.ts b/packages/agent-core/test/agent/resume.test.ts index 82d6e56ca..83df5fbc2 100644 --- a/packages/agent-core/test/agent/resume.test.ts +++ b/packages/agent-core/test/agent/resume.test.ts @@ -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 () => { diff --git a/packages/kosong/src/providers/kimi-files.ts b/packages/kosong/src/providers/kimi-files.ts index c0ea87c4f..75ca48e5e 100644 --- a/packages/kosong/src/providers/kimi-files.ts +++ b/packages/kosong/src/providers/kimi-files.ts @@ -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'; diff --git a/packages/migration-legacy/test/resume.integration.test.ts b/packages/migration-legacy/test/resume.integration.test.ts index 0383adf56..74d2e6455 100644 --- a/packages/migration-legacy/test/resume.integration.test.ts +++ b/packages/migration-legacy/test/resume.integration.test.ts @@ -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';