Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/upstream-036-web-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@moonshot-ai/kimi-code": minor
---

- web: port upstream 0.35/0.36 web UI improvements — composer work-mode pills (plan/goal) with a Swarm and Supermoon toolbar chip and an Add menu for arming modes, plus plan-armed intent; slash and @-mention menu restyle with pinyin fuzzy search and close-on-blur; assistant reply timestamps now show the message time; fullscreen image/video preview with image zoom; dock task status filter with a task detail pane and a subagent card grid; dock goal/plan tabs with a goal panel and plan viewer; session-title generation and workspace recency sorting; and baseline fixes (failure-card resume, flat sidebar view, failed-only badges, IME-safe renaming, skill-activation attachments, retry wording, approval-dock layout).
- web: add a plugin marketplace and capability management panel to Settings (installed plugins with enable/remove and content counts, marketplace browse and install, custom plugin install, capability install progress).
- web: cache content-hashed web assets as immutable (1 year) while revalidating the entry point, and remove the 64 MiB session-export limit.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions apps/kimi-code/dist-web/assets/index-C6dIaEdv.css

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion apps/kimi-code/dist-web/assets/index-DCnQUIG_.css

This file was deleted.

98 changes: 0 additions & 98 deletions apps/kimi-code/dist-web/assets/index-DVo-tvKU.js

This file was deleted.

100 changes: 100 additions & 0 deletions apps/kimi-code/dist-web/assets/index-pE-bC6n5.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/kimi-code/dist-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
the server's Content-Security-Policy forbids inline scripts. -->
<script src="/boot.js"></script>
<title>Kimi Code Web</title>
<script type="module" crossorigin src="/assets/index-DVo-tvKU.js"></script>
<script type="module" crossorigin src="/assets/index-pE-bC6n5.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-ucnN-Sh8.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-markstream-D6yjq9lL.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-katex-Com_2SPi.js">
<link rel="stylesheet" crossorigin href="/assets/vendor-4gi77x_E.css">
<link rel="stylesheet" crossorigin href="/assets/vendor-markstream-Boo68so5.css">
<link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
<link rel="stylesheet" crossorigin href="/assets/index-DCnQUIG_.css">
<link rel="stylesheet" crossorigin href="/assets/index-C6dIaEdv.css">
</head>
<body>
<div id="app"></div>
Expand Down
14 changes: 9 additions & 5 deletions apps/kimi-code/src/constant/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,15 @@ export const KIMI_CODE_CDN_LATEST_URL = `${KIMI_CODE_CDN_BASE}/latest`;
// bodies, and the CDN install scripts read it for fresh installs.
export const KIMI_CODE_CDN_LATEST_JSON_URL = `${KIMI_CODE_CDN_BASE}/latest.json`;
export const KIMI_CODE_TIPS_BANNER_URL = 'https://cdn.kimi.com/kimi-code-tips/tips.json';
// Plugin marketplace stays on the upstream CDN: the catalog content is
// upstream-hosted, and the fork's update channel does not mirror it.
export const KIMI_CODE_PLUGIN_MARKETPLACE_URL =
'https://code.kimi.com/kimi-code/plugins/marketplace.json';
export const KIMI_CODE_PLUGIN_MARKETPLACE_URL_ENV = 'KIMI_CODE_PLUGIN_MARKETPLACE_URL';
// The marketplace catalog location constants live in the shared
// agent-core-v2 plugin domain (kap-server consumes them from there).
// Deep-path import: this module is evaluated on every CLI invocation, so it
// must not pull in the engine root. Fork: the shared default is the upstream
// CDN URL — the fork's update channel does not mirror the catalog.
export {
KIMI_CODE_PLUGIN_MARKETPLACE_URL,
KIMI_CODE_PLUGIN_MARKETPLACE_URL_ENV,
} from '@moonshot-ai/agent-core-v2/app/plugin/marketplace';
// Official plugins whose usage bills against the user's plan quota. Installing
// one of these shows a quota note after the install result.
export const QUOTA_CONSUMING_PLUGIN_IDS: readonly string[] = ['kimi-datasource'];
Expand Down
2 changes: 0 additions & 2 deletions docs/en/guides/sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ You can also export from inside the TUI without leaving the interactive session:

In the web UI, `/export` downloads the current session as a diagnostic ZIP. It includes the persisted session data, diagnostic logs, and a bounded metadata-only `logs/kimi-web.jsonl` record of key browser events. Prompt text, WebSocket payloads, and console arguments are not copied into this browser log. This web command differs from the TUI `/export` alias above.

The browser buffers the ZIP before saving it, so web exports are limited to 64 MiB. For a larger session, use `kimi export <sessionId>` or the TUI `/export-debug-zip` command.

::: tip
Exported files may contain code, command output, and file paths that are sensitive. Review the content before sharing.
:::
Expand Down
2 changes: 0 additions & 2 deletions docs/zh/guides/sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ kimi export <sessionId> -o ~/Desktop/my-session.zip

在 web UI 中,`/export` 会把当前会话下载为诊断 ZIP。压缩包包含持久化的会话数据、诊断日志,以及记录浏览器关键事件且大小有上限、只含元数据的 `logs/kimi-web.jsonl`;提示词正文、WebSocket 内容和 console 参数不会写入这份浏览器日志。这里的 web 命令与上面的 TUI `/export` 别名行为不同。

浏览器需要先把 ZIP 缓存在内存中再保存,因此 web 导出上限为 64 MiB。更大的会话请使用 `kimi export <sessionId>` 或 TUI 的 `/export-debug-zip`。

::: tip 提示
导出文件可能包含代码、命令输出和路径等敏感信息,分享前请先确认内容。
:::
Expand Down
1 change: 1 addition & 0 deletions packages/agent-core-v2/docs/state-manifest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ export interface AgentStateSnapshot {
readonly kind: 'agent';
readonly agentId?: string;
readonly subagentType?: string;
readonly parentToolCallId?: string;
readonly model?: string;
readonly thinkingEffort?: string;
readonly taskId: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/agent-core-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"pathe": "^2.0.3",
"picomatch": "^4.0.4",
"retry": "0.13.1",
"semver": "^7.7.4",
"smol-toml": "^1.6.1",
"socks": "^2.8.9",
"tar": "^7.5.22",
Expand All @@ -93,6 +94,7 @@
"@types/js-yaml": "^4.0.9",
"@types/picomatch": "^4.0.3",
"@types/retry": "0.12.0",
"@types/semver": "^7.7.0",
"@types/sinon": "^21.0.1",
"@types/tar": "^7.0.87",
"@types/yauzl": "^2.10.3",
Expand Down
1 change: 1 addition & 0 deletions packages/agent-core-v2/src/agent/tools/agent/agentTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ export class SubagentTool implements ISubagentTool {
return {
agentId,
profileName,
parentToolCallId: toolCallId,
model: displayModel,
thinkingEffort: this.lifecycle
.get(agentId)
Expand Down
5 changes: 5 additions & 0 deletions packages/agent-core-v2/src/agent/tools/agent/subagent-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type SubagentCompletion = {
export type SubagentHandle = {
readonly agentId: string;
readonly profileName: string;
readonly parentToolCallId?: string;
readonly model?: string;
readonly thinkingEffort?: string;
readonly completion: Promise<SubagentCompletion>;
Expand All @@ -36,6 +37,7 @@ export interface SubagentTaskInfo extends AgentTaskInfoBase {
readonly kind: 'agent';
readonly agentId?: string;
readonly subagentType?: string;
readonly parentToolCallId?: string;
readonly model?: string;
readonly thinkingEffort?: string;
}
Expand Down Expand Up @@ -84,6 +86,7 @@ export class SubagentTask implements AgentTask {
readonly idPrefix: string = 'agent';
readonly agentId: string;
readonly subagentType: string;
readonly parentToolCallId?: string;
readonly model?: string;
readonly thinkingEffort?: string;

Expand All @@ -94,6 +97,7 @@ export class SubagentTask implements AgentTask {
) {
this.agentId = handle.agentId;
this.subagentType = handle.profileName;
this.parentToolCallId = handle.parentToolCallId;
this.model = handle.model;
this.thinkingEffort = handle.thinkingEffort;
}
Expand Down Expand Up @@ -129,6 +133,7 @@ export class SubagentTask implements AgentTask {
kind: 'agent',
agentId: this.agentId,
subagentType: this.subagentType,
parentToolCallId: this.parentToolCallId,
model: this.model,
thinkingEffort: this.thinkingEffort,
};
Expand Down
12 changes: 10 additions & 2 deletions packages/agent-core-v2/src/app/capability/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@
* Manages the built-in product capabilities (`kimi-cu`, `kimi-webbridge`):
* layered readiness detection and idempotent install orchestration. Entries
* are hardcoded in a closed registry — install sources are fixed official
* CDN URLs, never client-supplied.
* CDN URLs, never client-supplied. Install progress transitions are published
* through `onDidChangeInstall` (start / step / settle / error).
* `describeCapabilities` answers the static registry without running any
* detection probes.
*/

import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
import type { Event } from '#/_base/event';

import type { CapabilityStatus } from './types';
import type { CapabilityDescriptor, CapabilityInstallChange, CapabilityStatus } from './types';

export interface ICapabilityService {
readonly _serviceBrand: undefined;

readonly onDidChangeInstall: Event<CapabilityInstallChange>;

describeCapabilities(): readonly CapabilityDescriptor[];

listCapabilities(): Promise<readonly CapabilityStatus[]>;

getCapability(id: string): Promise<CapabilityStatus>;
Expand Down
49 changes: 38 additions & 11 deletions packages/agent-core-v2/src/app/capability/capabilityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
* `capability` domain (L3) — `ICapabilityService` implementation.
*
* 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 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.
* install runs per entry. Install progress lives in memory only; clients poll
* it or subscribe to `onDidChangeInstall` (fired on every transition), and a
* failed attempt leaves its error in the progress state 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 } from '#/app/scopes';
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
import { Disposable } from '#/_base/di/lifecycle';
import { Emitter, type Event } from '#/_base/event';
import { ILogService } from '#/_base/log/log';
import { Error2 } from '#/errors';
import { IBootstrapService } from '#/app/bootstrap/bootstrap';
Expand All @@ -26,27 +29,41 @@ import { createKimiWebbridgeEntry } from './entries/kimiWebbridge';
import type {
CapabilityEntry,
CapabilityId,
CapabilityDescriptor,
CapabilityInstallChange,
CapabilityInstallProgress,
CapabilityReadiness,
CapabilityStatus,
} from './types';

const IDLE_PROGRESS: CapabilityInstallProgress = { running: false };

export class CapabilityService implements ICapabilityService {
export class CapabilityService extends Disposable implements ICapabilityService {
declare readonly _serviceBrand: undefined;

private readonly onDidChangeInstallEmitter = this._register(
new Emitter<CapabilityInstallChange>(),
);
readonly onDidChangeInstall: Event<CapabilityInstallChange> =
this.onDidChangeInstallEmitter.event;

private readonly entries: ReadonlyMap<CapabilityId, CapabilityEntry>;
private readonly installProgress = new Map<CapabilityId, CapabilityInstallProgress>();
private readonly runningInstalls = new Set<CapabilityId>();

private setInstallProgress(id: CapabilityId, progress: CapabilityInstallProgress): void {
this.installProgress.set(id, progress);
this.onDidChangeInstallEmitter.fire({ id, install: progress });
}

constructor(
@IBootstrapService bootstrap: IBootstrapService,
@IPluginService plugins: IPluginService,
@IHostProcessService hostProcess: IHostProcessService,
@ILogService private readonly log: ILogService,
entriesOverride?: readonly CapabilityEntry[],
) {
super();
if (entriesOverride !== undefined) {
this.entries = new Map(entriesOverride.map((entry) => [entry.id, entry]));
} else {
Expand All @@ -65,6 +82,16 @@ export class CapabilityService implements ICapabilityService {
}
}

describeCapabilities(): readonly CapabilityDescriptor[] {
return [...this.entries.values()].map((entry) => ({
id: entry.id,
pluginId: entry.pluginId,
displayName: entry.displayName,
description: entry.description,
supported: entry.supported,
}));
}

listCapabilities(): Promise<readonly CapabilityStatus[]> {
return Promise.all([...this.entries.values()].map((entry) => this.statusOfSafe(entry)));
}
Expand All @@ -90,24 +117,24 @@ export class CapabilityService implements ICapabilityService {
}

this.runningInstalls.add(entry.id);
this.installProgress.set(entry.id, { running: true });
this.setInstallProgress(entry.id, { running: true });
void (async () => {
try {
await entry.install((step, percent) => {
this.installProgress.set(
const note = await entry.install((step, percent) => {
this.setInstallProgress(
entry.id,
percent === undefined ? { running: true, step } : { running: true, step, percent },
);
});
this.installProgress.set(entry.id, { running: false });
this.setInstallProgress(entry.id, { running: false, note });
} 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, {
this.setInstallProgress(entry.id, {
running: false,
error: error instanceof Error ? error.message : String(error),
});
Expand Down
6 changes: 4 additions & 2 deletions packages/agent-core-v2/src/app/capability/entries/kimiCu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ function createMacKimiCuEntry(ctx: CapabilityEntryContext): CapabilityEntry {
}
}

async function install(report: CapabilityInstallReporter): Promise<void> {
async function install(report: CapabilityInstallReporter): Promise<string | undefined> {
if (!supported) {
throw new Error(`kimi-cu is only supported on macOS (current: ${ctx.platform})`);
}
Expand Down Expand Up @@ -514,6 +514,7 @@ function createMacKimiCuEntry(ctx: CapabilityEntryContext): CapabilityEntry {
{ timeout: PERMISSIONS_TIMEOUT_MS },
).catch(() => undefined);
}
return undefined;
}

return {
Expand Down Expand Up @@ -630,7 +631,7 @@ function createWindowsKimiCuEntry(ctx: CapabilityEntryContext): CapabilityEntry
};
}

async function install(report: CapabilityInstallReporter): Promise<void> {
async function install(report: CapabilityInstallReporter): Promise<string | undefined> {
if (!supported) {
throw new Error(
`kimi-cu is only supported on macOS or Windows x64 (current: ${ctx.platform}/${ctx.arch})`,
Expand Down Expand Up @@ -710,6 +711,7 @@ function createWindowsKimiCuEntry(ctx: CapabilityEntryContext): CapabilityEntry
);
}
}
return undefined;
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export function createKimiWebbridgeEntry(ctx: CapabilityEntryContext): Capabilit
throw new Error(`WebBridge daemon did not come up on ${baseUrl} — check ~/.kimi-webbridge/logs`);
}

async function install(report: CapabilityInstallReporter): Promise<void> {
async function install(report: CapabilityInstallReporter): Promise<string | undefined> {
const asset = binaryAssetName(ctx.platform, ctx.arch);
if (asset === undefined) {
throw new Error(`kimi-webbridge is not supported on ${ctx.platform}/${ctx.arch}`);
Expand Down Expand Up @@ -251,6 +251,9 @@ export function createKimiWebbridgeEntry(ctx: CapabilityEntryContext): Capabilit
`Could not back up the standalone kimi-webbridge skill: ${error instanceof Error ? error.message : String(error)}`;
}
}
return standaloneSkillMigrationPending && standaloneSkillMigrationError === undefined
? 'user-skill-migrated'
: undefined;
}

async function installBinary(
Expand Down
16 changes: 15 additions & 1 deletion packages/agent-core-v2/src/app/capability/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface CapabilityInstallProgress {
readonly step?: string;
readonly percent?: number;
readonly error?: string;
readonly note?: string;
}

export interface CapabilityDetectResult {
Expand All @@ -49,12 +50,25 @@ export interface CapabilityStatus {

export type CapabilityInstallReporter = (step: string, percent?: number) => void;

export interface CapabilityDescriptor {
readonly id: CapabilityId;
readonly pluginId?: string;
readonly displayName: string;
readonly description: string;
readonly supported: boolean;
}

export interface CapabilityInstallChange {
readonly id: CapabilityId;
readonly install: CapabilityInstallProgress;
}

export interface CapabilityEntry {
readonly id: CapabilityId;
readonly pluginId?: string;
readonly displayName: string;
readonly description: string;
readonly supported: boolean;
detect(): Promise<CapabilityDetectResult>;
install(report: CapabilityInstallReporter): Promise<void>;
install(report: CapabilityInstallReporter): Promise<string | undefined>;
}
Loading
Loading