kimi-plugin-cc 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +5 -3
- package/.mcp.json +13 -0
- package/CHANGELOG.md +10 -0
- package/README.md +57 -5
- package/dist/cli-client.js +11 -1
- package/dist/commands/ask.js +4 -2
- package/dist/commands/cancel.js +43 -4
- package/dist/commands/pursue.js +3 -2
- package/dist/commands/rescue.js +3 -2
- package/dist/commands/review-gate.js +2 -4
- package/dist/commands/review.js +4 -2
- package/dist/commands/swarm.js +4 -3
- package/dist/companion.js +13 -40
- package/dist/help.js +2 -0
- package/dist/job-id.js +12 -0
- package/dist/job-store.js +59 -0
- package/dist/jobs.js +9 -1
- package/dist/mcp/binding.js +58 -0
- package/dist/mcp/job-control.js +345 -0
- package/dist/mcp/launcher.js +257 -0
- package/dist/mcp/operations.js +301 -0
- package/dist/mcp/protocol.js +275 -0
- package/dist/mcp/server.js +8 -0
- package/dist/mcp/stdio.js +78 -0
- package/dist/mcp/supervised-child.js +15 -0
- package/dist/mcp/tool-result.js +7 -0
- package/dist/mcp/workspace.js +34 -0
- package/dist/model-command.js +32 -0
- package/dist/version.js +1 -1
- package/docs/migration.md +2 -2
- package/docs/registry-releases.md +6 -8
- package/docs/safety.md +16 -2
- package/package.json +7 -3
- package/plugins/kimi-codex/.codex-plugin/plugin.json +7 -4
- package/plugins/kimi-codex/.mcp.json +28 -0
- package/plugins/kimi-codex/dist/cli-client.js +11 -1
- package/plugins/kimi-codex/dist/commands/ask.js +4 -2
- package/plugins/kimi-codex/dist/commands/cancel.js +43 -4
- package/plugins/kimi-codex/dist/commands/pursue.js +3 -2
- package/plugins/kimi-codex/dist/commands/rescue.js +3 -2
- package/plugins/kimi-codex/dist/commands/review-gate.js +2 -4
- package/plugins/kimi-codex/dist/commands/review.js +4 -2
- package/plugins/kimi-codex/dist/commands/swarm.js +4 -3
- package/plugins/kimi-codex/dist/companion.js +13 -40
- package/plugins/kimi-codex/dist/help.js +2 -0
- package/plugins/kimi-codex/dist/job-id.js +12 -0
- package/plugins/kimi-codex/dist/job-store.js +59 -0
- package/plugins/kimi-codex/dist/jobs.js +9 -1
- package/plugins/kimi-codex/dist/mcp/binding.js +58 -0
- package/plugins/kimi-codex/dist/mcp/job-control.js +345 -0
- package/plugins/kimi-codex/dist/mcp/launcher.js +257 -0
- package/plugins/kimi-codex/dist/mcp/operations.js +301 -0
- package/plugins/kimi-codex/dist/mcp/protocol.js +275 -0
- package/plugins/kimi-codex/dist/mcp/server.js +8 -0
- package/plugins/kimi-codex/dist/mcp/stdio.js +78 -0
- package/plugins/kimi-codex/dist/mcp/supervised-child.js +15 -0
- package/plugins/kimi-codex/dist/mcp/tool-result.js +7 -0
- package/plugins/kimi-codex/dist/mcp/workspace.js +34 -0
- package/plugins/kimi-codex/dist/model-command.js +32 -0
- package/plugins/kimi-codex/dist/version.js +1 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { formatError, RuntimeError } from "../errors.js";
|
|
2
|
+
import { KIMI_PLUGIN_CC_VERSION } from "../version.js";
|
|
3
|
+
import { cancelMcpJob, getMcpJobResult, getMcpJobStatus, getMcpTaskView } from "./job-control.js";
|
|
4
|
+
import { launchMcpOperation } from "./launcher.js";
|
|
5
|
+
import { buildMcpOperationInvocation, isMcpModelToolName, MCP_TOOL_DEFINITIONS, } from "./operations.js";
|
|
6
|
+
import { textResult } from "./tool-result.js";
|
|
7
|
+
const MODERN_VERSION = "2026-07-28";
|
|
8
|
+
const LEGACY_VERSIONS = new Set(["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"]);
|
|
9
|
+
const PROTOCOL_VERSION_META = "io.modelcontextprotocol/protocolVersion";
|
|
10
|
+
const CLIENT_INFO_META = "io.modelcontextprotocol/clientInfo";
|
|
11
|
+
const CLIENT_CAPABILITIES_META = "io.modelcontextprotocol/clientCapabilities";
|
|
12
|
+
const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
13
|
+
const SERVER_INFO_META = "io.modelcontextprotocol/serverInfo";
|
|
14
|
+
export function createMcpMessageHandler(env) {
|
|
15
|
+
let mode;
|
|
16
|
+
return async (message) => {
|
|
17
|
+
let requestId = isRecord(message) && "id" in message &&
|
|
18
|
+
(typeof message.id === "string" || typeof message.id === "number" || message.id === null)
|
|
19
|
+
? message.id
|
|
20
|
+
: null;
|
|
21
|
+
try {
|
|
22
|
+
const request = parseRequest(message);
|
|
23
|
+
if (!request)
|
|
24
|
+
return undefined;
|
|
25
|
+
requestId = request.id;
|
|
26
|
+
const { id, method, params } = request;
|
|
27
|
+
if (method === "server/discover") {
|
|
28
|
+
requireModernMetadata(params);
|
|
29
|
+
mode = "modern";
|
|
30
|
+
return response(id, {
|
|
31
|
+
resultType: "complete",
|
|
32
|
+
supportedVersions: [MODERN_VERSION],
|
|
33
|
+
capabilities: { tools: {}, extensions: { [TASKS_EXTENSION]: {} } },
|
|
34
|
+
_meta: { [SERVER_INFO_META]: { name: "kimi-plugin-cc", version: KIMI_PLUGIN_CC_VERSION } },
|
|
35
|
+
ttlMs: 0,
|
|
36
|
+
cacheScope: "private",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (method === "initialize") {
|
|
40
|
+
const requested = params.protocolVersion;
|
|
41
|
+
if (typeof requested !== "string" || !LEGACY_VERSIONS.has(requested)) {
|
|
42
|
+
throw rpcError(-32_602, "Unsupported protocol version", { supportedVersions: [...LEGACY_VERSIONS] });
|
|
43
|
+
}
|
|
44
|
+
mode = "legacy";
|
|
45
|
+
return response(id, {
|
|
46
|
+
protocolVersion: requested,
|
|
47
|
+
capabilities: { tools: {} },
|
|
48
|
+
serverInfo: { name: "kimi-plugin-cc", version: KIMI_PLUGIN_CC_VERSION },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (method === "notifications/initialized")
|
|
52
|
+
return undefined;
|
|
53
|
+
if (method.startsWith("notifications/"))
|
|
54
|
+
return undefined;
|
|
55
|
+
if (!mode && hasModernVersion(params))
|
|
56
|
+
mode = "modern";
|
|
57
|
+
if (!mode)
|
|
58
|
+
throw rpcError(-32_002, "Server is not initialized");
|
|
59
|
+
const modern = requireProtocolMode(mode, method);
|
|
60
|
+
const capabilities = modern ? requireModernMetadata(params) : undefined;
|
|
61
|
+
switch (method) {
|
|
62
|
+
case "ping":
|
|
63
|
+
return response(id, modern ? { resultType: "complete" } : {});
|
|
64
|
+
case "tools/list":
|
|
65
|
+
return response(id, modern
|
|
66
|
+
? { resultType: "complete", tools: MCP_TOOL_DEFINITIONS, ttlMs: 0, cacheScope: "private" }
|
|
67
|
+
: { tools: MCP_TOOL_DEFINITIONS });
|
|
68
|
+
case "tools/call":
|
|
69
|
+
return response(id, await callTool(env, params, modern, capabilities));
|
|
70
|
+
case "tasks/get": {
|
|
71
|
+
requireModern(method, modern);
|
|
72
|
+
requireTasksCapability(method, capabilities);
|
|
73
|
+
const taskId = requiredTaskId(params);
|
|
74
|
+
try {
|
|
75
|
+
const view = await getMcpTaskView(env, { job_id: taskId });
|
|
76
|
+
return response(id, { resultType: "complete", ...view });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof RuntimeError && error.code === "JOB_NOT_FOUND") {
|
|
80
|
+
throw rpcError(-32_602, `Unknown task ID: ${taskId}`);
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
case "tasks/cancel": {
|
|
86
|
+
requireModern(method, modern);
|
|
87
|
+
requireTasksCapability(method, capabilities);
|
|
88
|
+
const taskId = requiredTaskId(params);
|
|
89
|
+
try {
|
|
90
|
+
await cancelMcpJob(env, { job_id: taskId });
|
|
91
|
+
return response(id, { resultType: "complete" });
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof RuntimeError && error.code === "JOB_NOT_FOUND") {
|
|
95
|
+
throw rpcError(-32_602, `Unknown task ID: ${taskId}`);
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
default:
|
|
101
|
+
throw rpcError(-32_601, `Method not found: ${method}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (isRpcError(error))
|
|
106
|
+
return response(requestId, undefined, error);
|
|
107
|
+
if (error instanceof RuntimeError) {
|
|
108
|
+
return response(requestId, undefined, rpcError(-32_603, formatError(error), error.details));
|
|
109
|
+
}
|
|
110
|
+
return response(requestId, undefined, rpcError(-32_603, error instanceof Error ? error.message : String(error)));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function callTool(env, params, modern, capabilities) {
|
|
115
|
+
const name = params.name;
|
|
116
|
+
if (typeof name !== "string")
|
|
117
|
+
throw rpcError(-32_602, "tools/call requires a tool name");
|
|
118
|
+
try {
|
|
119
|
+
let result;
|
|
120
|
+
if (isMcpModelToolName(name)) {
|
|
121
|
+
const launched = await launchMcpOperation(buildMcpOperationInvocation(name, params.arguments), env);
|
|
122
|
+
if (modern && hasTasksCapability(capabilities))
|
|
123
|
+
return taskHandle(launched);
|
|
124
|
+
result = launched.job.status === "running"
|
|
125
|
+
? launchResult(launched)
|
|
126
|
+
: await getMcpJobResult(env, { job_id: launched.task.task_id });
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
switch (name) {
|
|
130
|
+
case "kimi_job_status":
|
|
131
|
+
result = await getMcpJobStatus(env, params.arguments);
|
|
132
|
+
break;
|
|
133
|
+
case "kimi_job_result":
|
|
134
|
+
result = await getMcpJobResult(env, params.arguments);
|
|
135
|
+
break;
|
|
136
|
+
case "kimi_job_cancel":
|
|
137
|
+
result = await cancelMcpJob(env, params.arguments);
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
throw rpcError(-32_602, `Unknown tool: ${name}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return modern ? { resultType: "complete", ...result } : result;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (isRpcError(error))
|
|
147
|
+
throw error;
|
|
148
|
+
if (error instanceof RuntimeError) {
|
|
149
|
+
const result = textResult(formatError(error), {
|
|
150
|
+
error: { code: error.code, stage: error.stage, details: error.details },
|
|
151
|
+
}, true);
|
|
152
|
+
return modern ? { resultType: "complete", ...result } : result;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function launchResult(launched) {
|
|
158
|
+
const { task, job } = launched;
|
|
159
|
+
const metadata = {
|
|
160
|
+
job_id: task.task_id,
|
|
161
|
+
operation_kind: task.operation_kind,
|
|
162
|
+
workspace: task.workspace_cwd,
|
|
163
|
+
status: job.status,
|
|
164
|
+
phase: job.phase,
|
|
165
|
+
created_at: task.created_at,
|
|
166
|
+
};
|
|
167
|
+
return textResult(`Started ${task.operation_kind} job ${task.task_id}. This is a launch handle, not the finished answer. Use kimi_job_status and kimi_job_result with this job_id until the job is terminal; use kimi_job_cancel to stop it.`, metadata);
|
|
168
|
+
}
|
|
169
|
+
function taskHandle({ task, job }) {
|
|
170
|
+
return {
|
|
171
|
+
resultType: "task",
|
|
172
|
+
taskId: task.task_id,
|
|
173
|
+
status: job.status === "running" ? "working" : job.status === "cancelled" ? "cancelled" : "completed",
|
|
174
|
+
...(job.status === "running" ? { statusMessage: `${task.operation_kind}: ${job.phase ?? "running"}` } : job.summary ? { statusMessage: job.summary } : {}),
|
|
175
|
+
createdAt: task.created_at,
|
|
176
|
+
lastUpdatedAt: job.updated_at,
|
|
177
|
+
ttlMs: null,
|
|
178
|
+
pollIntervalMs: 2_000,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function parseRequest(message) {
|
|
182
|
+
if (!isRecord(message))
|
|
183
|
+
throw new McpJsonRpcError(-32_600, "Invalid JSON-RPC request");
|
|
184
|
+
if (!("method" in message))
|
|
185
|
+
return undefined;
|
|
186
|
+
if (message.jsonrpc !== "2.0" || typeof message.method !== "string" || message.method.length === 0) {
|
|
187
|
+
throw new McpJsonRpcError(-32_600, "Invalid JSON-RPC request");
|
|
188
|
+
}
|
|
189
|
+
if (!("id" in message))
|
|
190
|
+
return undefined;
|
|
191
|
+
const id = message.id;
|
|
192
|
+
if (id !== null && typeof id !== "string" &&
|
|
193
|
+
(typeof id !== "number" || !Number.isFinite(id))) {
|
|
194
|
+
throw new McpJsonRpcError(-32_600, "Invalid JSON-RPC request ID");
|
|
195
|
+
}
|
|
196
|
+
const params = message.params === undefined ? {} : message.params;
|
|
197
|
+
if (!isRecord(params))
|
|
198
|
+
throw new McpJsonRpcError(-32_602, "Request params must be an object");
|
|
199
|
+
return { id, method: message.method, params };
|
|
200
|
+
}
|
|
201
|
+
function requireProtocolMode(mode, method) {
|
|
202
|
+
if (mode === "modern")
|
|
203
|
+
return true;
|
|
204
|
+
if (method === "tasks/get" || method === "tasks/cancel") {
|
|
205
|
+
throw new McpJsonRpcError(-32_601, `Method not found: ${method}`);
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
function requireModern(method, modern) {
|
|
210
|
+
if (!modern)
|
|
211
|
+
throw new McpJsonRpcError(-32_601, `Method not found: ${method}`);
|
|
212
|
+
}
|
|
213
|
+
function hasModernVersion(params) {
|
|
214
|
+
return isRecord(params._meta) && params._meta[PROTOCOL_VERSION_META] === MODERN_VERSION;
|
|
215
|
+
}
|
|
216
|
+
function requireModernMetadata(params) {
|
|
217
|
+
const meta = params._meta;
|
|
218
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
219
|
+
throw new McpJsonRpcError(-32_602, "Modern MCP requests require protocol metadata");
|
|
220
|
+
}
|
|
221
|
+
const record = meta;
|
|
222
|
+
if (record[PROTOCOL_VERSION_META] !== MODERN_VERSION) {
|
|
223
|
+
throw new McpJsonRpcError(-32_022, "Unsupported protocol version", {
|
|
224
|
+
supported: [MODERN_VERSION],
|
|
225
|
+
requested: record[PROTOCOL_VERSION_META],
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (record[CLIENT_INFO_META] !== undefined && !isRecord(record[CLIENT_INFO_META])) {
|
|
229
|
+
throw new McpJsonRpcError(-32_602, "clientInfo must be an object when supplied");
|
|
230
|
+
}
|
|
231
|
+
if (!isRecord(record[CLIENT_CAPABILITIES_META])) {
|
|
232
|
+
throw new McpJsonRpcError(-32_602, "Modern MCP requests require client capabilities");
|
|
233
|
+
}
|
|
234
|
+
return record[CLIENT_CAPABILITIES_META];
|
|
235
|
+
}
|
|
236
|
+
function hasTasksCapability(capabilities) {
|
|
237
|
+
if (!capabilities || !isRecord(capabilities.extensions))
|
|
238
|
+
return false;
|
|
239
|
+
return isRecord(capabilities.extensions[TASKS_EXTENSION]);
|
|
240
|
+
}
|
|
241
|
+
function requireTasksCapability(method, capabilities) {
|
|
242
|
+
if (!hasTasksCapability(capabilities)) {
|
|
243
|
+
throw new McpJsonRpcError(-32_021, "Missing required client capability", {
|
|
244
|
+
requiredCapabilities: { extensions: { [TASKS_EXTENSION]: {} } },
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function requiredTaskId(params) {
|
|
249
|
+
if (typeof params.taskId !== "string" || params.taskId.length === 0 || params.taskId.length > 128 || !/^[\x21-\x7e]+$/.test(params.taskId)) {
|
|
250
|
+
throw new McpJsonRpcError(-32_602, "taskId must be 1-128 printable ASCII characters without spaces");
|
|
251
|
+
}
|
|
252
|
+
return params.taskId;
|
|
253
|
+
}
|
|
254
|
+
function response(id, result, error) {
|
|
255
|
+
return error ? { jsonrpc: "2.0", id, error: { code: error.code, message: error.message, ...(error.data !== undefined ? { data: error.data } : {}) } } : { jsonrpc: "2.0", id, result };
|
|
256
|
+
}
|
|
257
|
+
function rpcError(code, message, data) {
|
|
258
|
+
return new McpJsonRpcError(code, message, data);
|
|
259
|
+
}
|
|
260
|
+
class McpJsonRpcError extends Error {
|
|
261
|
+
code;
|
|
262
|
+
data;
|
|
263
|
+
constructor(code, message, data) {
|
|
264
|
+
super(message);
|
|
265
|
+
this.code = code;
|
|
266
|
+
this.data = data;
|
|
267
|
+
this.name = "McpJsonRpcError";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function isRpcError(error) {
|
|
271
|
+
return error instanceof McpJsonRpcError || (error instanceof Error && "code" in error && typeof error.code === "number");
|
|
272
|
+
}
|
|
273
|
+
function isRecord(value) {
|
|
274
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
275
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
2
|
+
import { createMcpMessageHandler } from "./protocol.js";
|
|
3
|
+
import { serveMcpStdio } from "./stdio.js";
|
|
4
|
+
export async function runMcpServer(context) {
|
|
5
|
+
const paths = resolvePluginPaths(context.env);
|
|
6
|
+
await ensurePluginPaths(paths);
|
|
7
|
+
await serveMcpStdio(process.stdin, process.stdout, createMcpMessageHandler(context.env));
|
|
8
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const MAX_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
2
|
+
export async function serveMcpStdio(input, output, handle) {
|
|
3
|
+
let buffer = Buffer.alloc(0);
|
|
4
|
+
let writeTail = Promise.resolve();
|
|
5
|
+
let writeError;
|
|
6
|
+
const pending = new Set();
|
|
7
|
+
output.on("error", (error) => { writeError ??= error; });
|
|
8
|
+
const write = (message) => {
|
|
9
|
+
if (writeError)
|
|
10
|
+
return Promise.reject(writeError);
|
|
11
|
+
const frame = `${JSON.stringify(message)}\n`;
|
|
12
|
+
writeTail = writeTail.then(() => new Promise((resolve, reject) => {
|
|
13
|
+
try {
|
|
14
|
+
if (output.write(frame))
|
|
15
|
+
resolve();
|
|
16
|
+
else
|
|
17
|
+
output.once("drain", resolve);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
reject(error);
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
return writeTail;
|
|
24
|
+
};
|
|
25
|
+
const track = (promise) => {
|
|
26
|
+
const tracked = promise.catch((error) => { writeError ??= error; }).finally(() => pending.delete(tracked));
|
|
27
|
+
pending.add(tracked);
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
for await (const chunk of input) {
|
|
31
|
+
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
32
|
+
for (;;) {
|
|
33
|
+
const newline = buffer.indexOf(0x0a);
|
|
34
|
+
if (newline < 0)
|
|
35
|
+
break;
|
|
36
|
+
const line = buffer.subarray(0, newline);
|
|
37
|
+
buffer = buffer.subarray(newline + 1);
|
|
38
|
+
if (line.length > MAX_MESSAGE_BYTES)
|
|
39
|
+
throw new Error("MCP frame exceeds 8 MiB");
|
|
40
|
+
if (line.toString("utf8").trim().length === 0)
|
|
41
|
+
continue;
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(line.toString("utf8"));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
track(write({ jsonrpc: "2.0", id: null, error: { code: -32_700, message: "Parse error" } }));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
track((async () => {
|
|
51
|
+
try {
|
|
52
|
+
const response = await handle(parsed);
|
|
53
|
+
if (response !== undefined)
|
|
54
|
+
await write(response);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
await write({
|
|
58
|
+
jsonrpc: "2.0",
|
|
59
|
+
id: null,
|
|
60
|
+
error: { code: -32_603, message: error instanceof Error ? error.message : String(error) },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
})());
|
|
64
|
+
}
|
|
65
|
+
if (buffer.length > MAX_MESSAGE_BYTES)
|
|
66
|
+
throw new Error("MCP frame exceeds 8 MiB");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
await write({ jsonrpc: "2.0", id: null, error: { code: -32_700, message: error instanceof Error ? error.message : String(error) } });
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await Promise.all(pending);
|
|
74
|
+
await writeTail;
|
|
75
|
+
if (writeError)
|
|
76
|
+
throw writeError;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RuntimeError } from "../errors.js";
|
|
2
|
+
import { allocateJobId } from "../job-id.js";
|
|
3
|
+
import { runModelCommand } from "../model-command.js";
|
|
4
|
+
export async function runMcpChild(argv, context) {
|
|
5
|
+
const [jobId, ...command] = argv;
|
|
6
|
+
if (!jobId || command.length === 0) {
|
|
7
|
+
throw new RuntimeError("MCP_INVALID_CHILD", "mcp-child expects a job UUID followed by one model operation.", "mcp.child");
|
|
8
|
+
}
|
|
9
|
+
const mcpJobId = allocateJobId({ ...context, mcpJobId: jobId });
|
|
10
|
+
const result = await runModelCommand(command, { ...context, mcpJobId });
|
|
11
|
+
if (result === undefined) {
|
|
12
|
+
throw new RuntimeError("MCP_INVALID_CHILD", "mcp-child accepts only the public model operations.", "mcp.child");
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { realpath, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { RuntimeError } from "../errors.js";
|
|
4
|
+
import { resolveRepoIdentity } from "../git.js";
|
|
5
|
+
export async function resolveMcpWorkspace(env, requested) {
|
|
6
|
+
const defaultCandidate = env.KIMI_PLUGIN_CC_MCP_WORKSPACE;
|
|
7
|
+
const candidate = requested ?? defaultCandidate;
|
|
8
|
+
if (!candidate) {
|
|
9
|
+
throw new RuntimeError("MCP_WORKSPACE_REQUIRED", "This MCP server has no default workspace. Supply the absolute workspace directory explicitly; Codex calls normally require it.", "mcp.workspace");
|
|
10
|
+
}
|
|
11
|
+
const cwd = await canonicalDirectory(candidate, "workspace");
|
|
12
|
+
if (defaultCandidate) {
|
|
13
|
+
const defaultCwd = await canonicalDirectory(defaultCandidate, "host workspace");
|
|
14
|
+
const relative = path.relative(defaultCwd, cwd);
|
|
15
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
16
|
+
throw new RuntimeError("MCP_WORKSPACE_OUTSIDE_DEFAULT", `workspace must be the host workspace or one of its descendants, not ${cwd} (host: ${defaultCwd}).`, "mcp.workspace");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { cwd, repo: await resolveRepoIdentity(cwd) };
|
|
20
|
+
}
|
|
21
|
+
async function canonicalDirectory(candidate, label) {
|
|
22
|
+
if (!path.isAbsolute(candidate)) {
|
|
23
|
+
throw new RuntimeError("MCP_WORKSPACE_NOT_ABSOLUTE", `${label} must be an absolute existing directory.`, "mcp.workspace");
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const cwd = await realpath(candidate);
|
|
27
|
+
if (!(await stat(cwd)).isDirectory())
|
|
28
|
+
throw new Error("not a directory");
|
|
29
|
+
return cwd;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw new RuntimeError("MCP_WORKSPACE_INVALID", `${label} is not an accessible existing directory: ${candidate}`, "mcp.workspace", error instanceof Error ? { cause: error } : undefined);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { runAsk } from "./commands/ask.js";
|
|
2
|
+
import { notImplementedCompanionCommand } from "./commands/not-implemented.js";
|
|
3
|
+
import { runPursue } from "./commands/pursue.js";
|
|
4
|
+
import { runRescue } from "./commands/rescue.js";
|
|
5
|
+
import { runReview } from "./commands/review.js";
|
|
6
|
+
import { runSwarm } from "./commands/swarm.js";
|
|
7
|
+
export async function runModelCommand(argv, context) {
|
|
8
|
+
const [command, ...rest] = argv;
|
|
9
|
+
switch (command) {
|
|
10
|
+
case "review":
|
|
11
|
+
return `${await runReview(rest, context, "review")}\n`;
|
|
12
|
+
case "ask":
|
|
13
|
+
return `${await runAsk(rest, context)}\n`;
|
|
14
|
+
case "task": {
|
|
15
|
+
const [taskType, ...taskArgs] = rest;
|
|
16
|
+
switch (taskType) {
|
|
17
|
+
case "rescue":
|
|
18
|
+
return runRescue(taskArgs, context);
|
|
19
|
+
case "challenge":
|
|
20
|
+
return `${await runReview(taskArgs, context, "challenge")}\n`;
|
|
21
|
+
case "pursue":
|
|
22
|
+
return runPursue(taskArgs, context);
|
|
23
|
+
case "swarm":
|
|
24
|
+
return runSwarm(taskArgs, context);
|
|
25
|
+
default:
|
|
26
|
+
notImplementedCompanionCommand(taskType ? `task ${taskType}` : "task");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
default:
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/version.js
CHANGED
package/docs/migration.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Compatibility and upgrades
|
|
2
2
|
|
|
3
|
-
For plugin 2.
|
|
3
|
+
For plugin 2.4.0, use Kimi Code CLI 2.3.1. The plugin certifies exact native-v2 versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, and `2.3.1` for all eight operations. It does not automatically accept later patches or minor releases. The npm distribution adds an installation channel; it does not change this compatibility table.
|
|
4
4
|
|
|
5
5
|
Explicitly pinned legacy-v1 binaries remain supported within `KIMI_TESTED_MINORS`, through 0.41.x, subject to each operation's minimum version. The [runtime table](../runtime/kimi-engine.ts) is the authority.
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ Use the [README installation and update steps](../README.md) for Claude Code or
|
|
|
8
8
|
|
|
9
9
|
## Plugin versions and upgrades
|
|
10
10
|
|
|
11
|
-
Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.
|
|
11
|
+
Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.4.0 certifies CLI 2.3.1; the runtime table records that mapping. Published versions and tags are never reused. Every upstream patch requires its own certification. The native-v2 no-plan profile, plugin/marketplace IDs and existing job store are preserved.
|
|
12
12
|
|
|
13
13
|
After updating the plugin, run Claude Code `/kimi:setup` or Codex `$kimi-setup`
|
|
14
14
|
from that host's active install to re-pin its version-stamped hook path, then
|
|
@@ -6,7 +6,7 @@ remain available. The package has no install scripts and no separate CLI.
|
|
|
6
6
|
|
|
7
7
|
## Publishing status
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Plugin **2.3.1** certifies exact CLI **2.3.1**. [Release v2.3.1](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.3.1) is anchored at `20011ff0554c611afe0a426b30a68c2473175568`; its [CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/39977037533) and [release-event publishing run](https://github.com/linxule/kimi-plugin-cc/actions/runs/39977818482) passed. The registry tarball has SHA-1 `e309448480a2ce841b49840cdaeeed37acc6c50b`, contains 196 files, and matches the CI artifact byte-for-byte. See the [certification evidence](./upstream-2.3.1-certification.md).
|
|
10
10
|
The previous verified publication was **2.2.2**:
|
|
11
11
|
[Release v2.2.2](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.2.2)
|
|
12
12
|
is anchored at `256e78ff7cbf302fb19e8e523d8e3f943f7cb366`; its
|
|
@@ -78,8 +78,9 @@ bun run check:package .tmp/npm
|
|
|
78
78
|
|
|
79
79
|
The package check inspects the actual tarball, verifies runtime copies and
|
|
80
80
|
vendored licenses, and installs/checks the safety hook in isolated homes for
|
|
81
|
-
both hosts. It also exercises unsupported-version refusal using a fake CLI
|
|
82
|
-
|
|
81
|
+
both hosts. It also exercises unsupported-version refusal using a fake CLI and
|
|
82
|
+
modern MCP discovery plus legacy initialization on both extracted compiled
|
|
83
|
+
distributions. It does not invoke a model or read the maintainer's Kimi configuration.
|
|
83
84
|
|
|
84
85
|
## First publication (completed bootstrap reference)
|
|
85
86
|
|
|
@@ -119,8 +120,5 @@ release. `.github/workflows/publish.yml` runs on `release.published`; it checks
|
|
|
119
120
|
the tag against `package.json`, runs the full gate and package smoke, then
|
|
120
121
|
publishes the validated tarball through OIDC with provenance. The publish job
|
|
121
122
|
has no source checkout or build scripts. A manual dispatch with an existing
|
|
122
|
-
release tag is available for
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
A locally bootstrapped first version is already published: do not dispatch it
|
|
126
|
-
again. The next release is the first OIDC publication.
|
|
123
|
+
release tag is available for recovery after a verified publication failure.
|
|
124
|
+
Check npm before retrying; released versions are immutable.
|
package/docs/safety.md
CHANGED
|
@@ -229,6 +229,20 @@ If another installed host still points to an older hook, that hook may veto thes
|
|
|
229
229
|
- **Field-proven by a real-binary smoke.** `tests/runtime/real-binary-smoke.test.ts` runs the real `runSwarm --write` and asserts the POSITIVE end-to-end (a `coder` subagent's edits land in the worktree, captured as a patch; the user's real tree is byte-identical + git-clean; the worktree is cleaned up) plus the NEGATIVE (an absolute-path write outside the trusted root is hook-denied). It is the check that catches `tool_input` field-name drift — `Write`/`Edit` carry `path`, not `file_path`, and reading the wrong key denies every real write silently.
|
|
230
230
|
- **Model-invocable agent (v1.5), patch-only by construction.** Following the field-proof above, `--write` now has a `kimi-swarm-write` subagent so the main thread can dispatch a write fan-out on its own judgement — the same human-command + model-invocable-agent split the rest of the suite uses. Auto-dispatch is a safe extension here because it widens **no** write surface and removes **no** bound: the agent path runs the identical `runSwarm --write` code, so edits still land only in the throwaway worktree, the managed hook still gates every `coder` subagent, the patch is still handed back for the user to apply (**the plugin never applies or commits**), and `--budget` + default `--max-concurrency` 1 + the hook requirement all still hold. In fact write-swarm is a *less* dangerous agent than the `kimi-pursue` agent already shipped: pursue writes directly into the workspace, whereas write-swarm's output is a patch the human reviews before anything reaches a real tree. Because it is still a write surface, the agent description demands BOTH signals — many disjoint write targets AND explicit fan-out intent — and steers single edits to `kimi-rescue`, read-only fan-outs to `kimi-swarm`, and autonomous loops to `kimi-pursue`. Default `--max-concurrency` stays **1**; raising it (concurrent writers) remains a deliberate, human-or-explicitly-requested choice.
|
|
231
231
|
|
|
232
|
+
## MCP transport and long-running jobs
|
|
233
|
+
|
|
234
|
+
The plugin also bundles an MCP stdio server for Claude Code and Codex. This is an additive transport, not a second execution engine or a way around the managed hook. The seven model tools run the same certified companion operations, exact-version checks, native-v2 preflight, workspace allowlists, finite budgets, and artifact rendering as the slash commands and skills. The review gate remains a separate Stop-hook feature.
|
|
235
|
+
|
|
236
|
+
Claude Code task-capable calls return durable MCP task IDs and use `tasks/get` and `tasks/cancel`. Codex currently consumes ordinary MCP tool results, not this task extension: after attachment, model tools return a job handle when the job is still running or its terminal result when already settled. `kimi_job_status`, `kimi_job_result`, and `kimi_job_cancel` provide polling and cancellation. Do not describe the Codex path as native MCP tasks.
|
|
237
|
+
|
|
238
|
+
Claude supplies its project directory as the default workspace. A configured host workspace is a containment boundary: an explicit workspace must be that directory or a descendant, not an arbitrary override. Codex calls normally supply an absolute `workspace` on the initial operation because its packaged stdio server runs from the plugin root; that explicit path is the operation's trust choice. Subsequent job control can use the persisted binding. Job controls manage only MCP-launched jobs, require the same canonical workspace and repository, and cannot inspect or cancel an arbitrary CLI job by ID.
|
|
239
|
+
|
|
240
|
+
Attachment waits for the operation to install cancellation handlers, with a 60-second bound. Verified teardown can close a stalled launch; missing identity or inconclusive termination evidence keeps it starting and recoverable by ID. `kimi_pid` is recorded at actual spawn rather than inferred from a later process-list match.
|
|
241
|
+
|
|
242
|
+
The MCP task/job ID is preassigned and bound to the job's real `operation_kind`, not merely its lineage. `mcp_tasks` stores launch and ownership metadata; the normal `jobs` table remains the outcome authority. Failed jobs, including budget timeouts, produce completed tasks with `isError:true` and the failed-job artifact, so partial output is not mistaken for a final answer. Supervisor and protocol failures are distinct from ordinary Kimi failures.
|
|
243
|
+
|
|
244
|
+
Closing an MCP connection does not cancel work. A later server reopens the same store and resumes status/result handling by ID. Generic stale sweeps skip MCP-owned rows, and direct CLI cancellation refuses them; use MCP job controls for those jobs. Companion and observed Kimi-descendant identities include start tokens and process groups; MCP cancellation and stale cleanup revalidate them rather than signalling a recycled PID. Cancellation remains cooperative and does not roll back edits. Pursue teardown and write-swarm patch capture still run through the existing companion path; write-swarm captures its patch on every terminal path before removing the worktree.
|
|
245
|
+
|
|
232
246
|
## Response budgets and partial output (2.2.0)
|
|
233
247
|
|
|
234
248
|
Every single-turn command runs under a wall-clock budget: ask, review and
|
|
@@ -265,9 +279,9 @@ at it (the runtime resolves that path instead of `kimi` on `PATH`; every spawn
|
|
|
265
279
|
still exports `KIMI_CODE_NO_AUTO_UPDATE=1`, so the pinned copy never drifts).
|
|
266
280
|
|
|
267
281
|
```sh
|
|
268
|
-
PIN=~/.local/share/kimi-plugin-cc/kimi-bin/2.
|
|
282
|
+
PIN=~/.local/share/kimi-plugin-cc/kimi-bin/2.3.1
|
|
269
283
|
mkdir -p "$PIN" && cd "$PIN" && printf '{"name":"kimi-plugin-cc-pin","private":true}\n' > package.json
|
|
270
|
-
bun add @moonshot-ai/kimi-code@2.
|
|
284
|
+
bun add @moonshot-ai/kimi-code@2.3.1
|
|
271
285
|
export KIMI_PLUGIN_CC_KIMI_BIN="$PIN/node_modules/.bin/kimi" # e.g. in ~/.zshenv
|
|
272
286
|
```
|
|
273
287
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi-plugin-cc",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Kimi for Claude Code and Codex: code questions, independent reviews, bounded fixes, autonomous goals,
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Kimi for Claude Code and Codex: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP tools through the local Kimi Code CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.5.0"
|
|
@@ -38,14 +38,18 @@
|
|
|
38
38
|
"claude-code",
|
|
39
39
|
"codex",
|
|
40
40
|
"kimi",
|
|
41
|
+
"kimi-code",
|
|
41
42
|
"plugin",
|
|
42
|
-
"code-review"
|
|
43
|
+
"code-review",
|
|
44
|
+
"mcp",
|
|
45
|
+
"model-context-protocol"
|
|
43
46
|
],
|
|
44
47
|
"publishConfig": {
|
|
45
48
|
"access": "public",
|
|
46
49
|
"registry": "https://registry.npmjs.org/"
|
|
47
50
|
},
|
|
48
51
|
"files": [
|
|
52
|
+
".mcp.json",
|
|
49
53
|
".claude-plugin/",
|
|
50
54
|
".agents/plugins/marketplace.json",
|
|
51
55
|
"agents/",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Kimi for Codex: code questions, independent reviews, bounded fixes, autonomous goals,
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Kimi for Codex: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP job controls through the local Kimi Code CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "linxule"
|
|
7
7
|
},
|
|
@@ -14,13 +14,16 @@
|
|
|
14
14
|
"review",
|
|
15
15
|
"code-review",
|
|
16
16
|
"delegation",
|
|
17
|
-
"multi-model"
|
|
17
|
+
"multi-model",
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol"
|
|
18
20
|
],
|
|
19
21
|
"skills": "./skills/",
|
|
22
|
+
"mcpServers": "./.mcp.json",
|
|
20
23
|
"interface": {
|
|
21
24
|
"displayName": "Kimi",
|
|
22
25
|
"shortDescription": "Ask, review, and build with Kimi",
|
|
23
|
-
"longDescription": "Kimi Code workflows for Codex: read-only explanations and reviews, bounded workspace fixes, autonomous goals, and parallel tasks. Codex skills call a self-contained shell runtime
|
|
26
|
+
"longDescription": "Kimi Code workflows for Codex: read-only explanations and reviews, bounded workspace fixes, autonomous goals, and parallel tasks. Codex skills call a self-contained shell runtime, with MCP tools provided by the bundled server.",
|
|
24
27
|
"developerName": "linxule",
|
|
25
28
|
"category": "Developer Tools",
|
|
26
29
|
"capabilities": [
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"kimi": {
|
|
4
|
+
"type": "stdio",
|
|
5
|
+
"command": "./scripts/companion.sh",
|
|
6
|
+
"args": [
|
|
7
|
+
"mcp"
|
|
8
|
+
],
|
|
9
|
+
"cwd": ".",
|
|
10
|
+
"env_vars": [
|
|
11
|
+
"KIMI_PLUGIN_CC_DATA",
|
|
12
|
+
"CLAUDE_PLUGIN_DATA",
|
|
13
|
+
"PLUGIN_DATA",
|
|
14
|
+
"KIMI_CODE_HOME",
|
|
15
|
+
"KIMI_PLUGIN_CC_KIMI_BIN",
|
|
16
|
+
"KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS",
|
|
17
|
+
"KIMI_PLUGIN_CC_NODE_BIN",
|
|
18
|
+
"KIMI_PLUGIN_CC_HOOK_SCRIPT",
|
|
19
|
+
"KIMI_PLUGIN_CC_HOST_ID",
|
|
20
|
+
"KIMI_MODEL_NAME",
|
|
21
|
+
"KIMI_MODEL_API_KEY",
|
|
22
|
+
"KIMI_MODEL_PROVIDER_TYPE",
|
|
23
|
+
"KIMI_MODEL_BASE_URL",
|
|
24
|
+
"KIMI_MODEL_THINKING_EFFORT"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|