diff --git a/.changeset/kimi-cu-windows.md b/.changeset/kimi-cu-windows.md index 4df87f4c7..daa244a2e 100644 --- a/.changeset/kimi-cu-windows.md +++ b/.changeset/kimi-cu-windows.md @@ -2,4 +2,4 @@ "@moonshot-ai/kimi-code": minor --- -Add Windows support for the built-in Kimi Computer Use capability. Install it from `/plugins` on Windows x64. +Add Windows support for the built-in Kimi Computer Use capability and show the underlying error when capability setup fails. Install it from `/plugins` on Windows x64. diff --git a/apps/kimi-code/src/tui/commands/plugins.ts b/apps/kimi-code/src/tui/commands/plugins.ts index 604ff8dcf..031b8b17d 100644 --- a/apps/kimi-code/src/tui/commands/plugins.ts +++ b/apps/kimi-code/src/tui/commands/plugins.ts @@ -539,7 +539,8 @@ async function installCapabilityFromPanel( } logCapabilityStatus(result); if (result.install.error !== undefined) { - host.showError(`${label} installation failed. Check the logs and install again from /plugins.`); + host.showError(`${label} installation failed: ${result.install.error}`); + host.showStatus('Fix the reported error, then install again from /plugins.', 'warning'); return; } if (result.state !== 'ready') { diff --git a/apps/kimi-code/test/tui/commands/plugins-capability.test.ts b/apps/kimi-code/test/tui/commands/plugins-capability.test.ts index 817b1964c..dcdc767f7 100644 --- a/apps/kimi-code/test/tui/commands/plugins-capability.test.ts +++ b/apps/kimi-code/test/tui/commands/plugins-capability.test.ts @@ -199,6 +199,29 @@ describe('plugins command capability surface', () => { expect(statuses.some((s) => s.includes('is installed'))).toBe(true); }); + it('shows the engine error when a background capability install fails', async () => { + const { host, statuses } = fakeHost({ + engineV2: true, + capabilityStatus: () => + Promise.resolve({ + state: 'not_installed', + steps: [], + install: { running: false, error: 'Authenticode signature is not valid' }, + }), + }); + + await installCapabilityFromPanel( + host, + fakePanel().panel, + { id: 'kimi-cu', displayName: 'Kimi Computer Use', source: 'capability:kimi-cu' } as never, + ); + + expect(statuses).toContain( + 'Kimi Computer Use installation failed: Authenticode signature is not valid', + ); + expect(statuses).toContain('Fix the reported error, then install again from /plugins.'); + }); + it('shows required permissions once after installation instead of exposing step details', async () => { const { host, statuses } = fakeHost({ engineV2: true, diff --git a/packages/agent-core-v2/src/app/capability/capabilityService.ts b/packages/agent-core-v2/src/app/capability/capabilityService.ts index 682f54866..2903f403e 100644 --- a/packages/agent-core-v2/src/app/capability/capabilityService.ts +++ b/packages/agent-core-v2/src/app/capability/capabilityService.ts @@ -4,14 +4,15 @@ * Holds the closed registry of built-in capability entries and serializes * install runs per entry. Install progress lives in memory only and is * polled by clients; a failed attempt leaves its error in the progress state - * until the next attempt starts. Listing degrades a single entry's failing - * detection to a failed step on that entry instead of rejecting the whole - * list. Bound at App scope. + * until the next attempt starts and logs the failure through `log`. Listing + * degrades a single entry's failing detection to a failed step on that entry + * instead of rejecting the whole list. Bound at App scope. */ import { homedir } from 'node:os'; import { LifecycleScope, ScopeActivation, registerScopedService } from '#/_base/di/scope'; +import { ILogService } from '#/_base/log/log'; import { Error2 } from '#/errors'; import { IBootstrapService } from '#/app/bootstrap/bootstrap'; import { IPluginService } from '#/app/plugin/plugin'; @@ -42,6 +43,7 @@ export class CapabilityService implements ICapabilityService { @IBootstrapService bootstrap: IBootstrapService, @IPluginService plugins: IPluginService, @IHostProcessService hostProcess: IHostProcessService, + @ILogService private readonly log: ILogService, entriesOverride?: readonly CapabilityEntry[], ) { if (entriesOverride !== undefined) { @@ -98,6 +100,12 @@ export class CapabilityService implements ICapabilityService { }); this.installProgress.set(entry.id, { running: false }); } catch (error) { + const step = this.installProgress.get(entry.id)?.step; + this.log.warn('capability install failed', { + capabilityId: entry.id, + step, + error, + }); this.installProgress.set(entry.id, { running: false, error: error instanceof Error ? error.message : String(error), diff --git a/packages/agent-core-v2/test/app/capability/capabilityService.test.ts b/packages/agent-core-v2/test/app/capability/capabilityService.test.ts index 6424f0bf0..4deb64611 100644 --- a/packages/agent-core-v2/test/app/capability/capabilityService.test.ts +++ b/packages/agent-core-v2/test/app/capability/capabilityService.test.ts @@ -7,6 +7,7 @@ import { describe, expect, it } from 'vitest'; import { isError2 } from '#/_base/errors/errors'; +import type { ILogService, LogPayload } from '#/_base/log/log'; import { CapabilityErrors } from '#/app/capability/errors'; import { CapabilityService } from '#/app/capability/capabilityService'; import type { @@ -15,6 +16,8 @@ import type { CapabilityInstallReporter, } from '#/app/capability/types'; +import { stubLog } from '../../_base/log/stubs'; + function fakeEntry(overrides: { id: 'kimi-cu' | 'kimi-webbridge'; pluginId?: string; @@ -36,12 +39,16 @@ function fakeEntry(overrides: { }; } -function fakeService(entries: readonly CapabilityEntry[]): CapabilityService { +function fakeService( + entries: readonly CapabilityEntry[], + log: ILogService = stubLog(), +): CapabilityService { // bootstrap / hostProcess are unused when entries are injected. return new CapabilityService( undefined as never, undefined as never, undefined as never, + log, entries, ); } @@ -239,4 +246,42 @@ describe('CapabilityService', () => { expect(retried.install.error).toBeUndefined(); expect(attempts).toBe(2); }); + + it('logs an install error with its last progress step when setup fails', async () => { + const warnings: Array<{ message: string; payload?: LogPayload }> = []; + let resolveLogged: (() => void) | undefined; + const logged = new Promise((resolve) => { + resolveLogged = resolve; + }); + const error = new Error('signature mismatch'); + const log = { + ...stubLog(), + warn: (message: string, payload?: LogPayload) => { + warnings.push({ message, payload }); + resolveLogged?.(); + }, + } satisfies ILogService; + const service = fakeService( + [ + fakeEntry({ + id: 'kimi-cu', + install: async (report) => { + report('runtime'); + throw error; + }, + }), + ], + log, + ); + + await service.installCapability('kimi-cu'); + await logged; + + expect(warnings).toEqual([ + { + message: 'capability install failed', + payload: { capabilityId: 'kimi-cu', step: 'runtime', error }, + }, + ]); + }); });