kimi-plugin-cc 2.3.1 → 2.4.1

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.
Files changed (71) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +5 -3
  3. package/.mcp.json +13 -0
  4. package/CHANGELOG.md +19 -0
  5. package/README.md +67 -7
  6. package/dist/cli-client.js +13 -3
  7. package/dist/commands/ask.js +4 -2
  8. package/dist/commands/cancel.js +43 -4
  9. package/dist/commands/cli-helpers.js +5 -1
  10. package/dist/commands/pursue.js +3 -2
  11. package/dist/commands/rescue.js +3 -2
  12. package/dist/commands/review-gate.js +2 -4
  13. package/dist/commands/review.js +4 -2
  14. package/dist/commands/swarm.js +195 -37
  15. package/dist/companion.js +13 -40
  16. package/dist/help.js +2 -0
  17. package/dist/hooks/config-safety.js +4 -3
  18. package/dist/job-id.js +12 -0
  19. package/dist/job-store.js +59 -0
  20. package/dist/jobs.js +9 -1
  21. package/dist/kimi-engine.js +1 -0
  22. package/dist/mcp/binding.js +58 -0
  23. package/dist/mcp/job-control.js +345 -0
  24. package/dist/mcp/launcher.js +257 -0
  25. package/dist/mcp/operations.js +301 -0
  26. package/dist/mcp/protocol.js +275 -0
  27. package/dist/mcp/server.js +8 -0
  28. package/dist/mcp/stdio.js +78 -0
  29. package/dist/mcp/supervised-child.js +15 -0
  30. package/dist/mcp/tool-result.js +7 -0
  31. package/dist/mcp/workspace.js +34 -0
  32. package/dist/model-command.js +32 -0
  33. package/dist/process-tree.js +48 -17
  34. package/dist/swarm-activation.js +812 -0
  35. package/dist/version.js +1 -1
  36. package/docs/migration.md +12 -2
  37. package/docs/registry-releases.md +11 -9
  38. package/docs/safety.md +31 -5
  39. package/package.json +8 -4
  40. package/plugins/kimi-codex/.codex-plugin/plugin.json +7 -4
  41. package/plugins/kimi-codex/.mcp.json +28 -0
  42. package/plugins/kimi-codex/dist/cli-client.js +13 -3
  43. package/plugins/kimi-codex/dist/commands/ask.js +4 -2
  44. package/plugins/kimi-codex/dist/commands/cancel.js +43 -4
  45. package/plugins/kimi-codex/dist/commands/cli-helpers.js +5 -1
  46. package/plugins/kimi-codex/dist/commands/pursue.js +3 -2
  47. package/plugins/kimi-codex/dist/commands/rescue.js +3 -2
  48. package/plugins/kimi-codex/dist/commands/review-gate.js +2 -4
  49. package/plugins/kimi-codex/dist/commands/review.js +4 -2
  50. package/plugins/kimi-codex/dist/commands/swarm.js +195 -37
  51. package/plugins/kimi-codex/dist/companion.js +13 -40
  52. package/plugins/kimi-codex/dist/help.js +2 -0
  53. package/plugins/kimi-codex/dist/hooks/config-safety.js +4 -3
  54. package/plugins/kimi-codex/dist/job-id.js +12 -0
  55. package/plugins/kimi-codex/dist/job-store.js +59 -0
  56. package/plugins/kimi-codex/dist/jobs.js +9 -1
  57. package/plugins/kimi-codex/dist/kimi-engine.js +1 -0
  58. package/plugins/kimi-codex/dist/mcp/binding.js +58 -0
  59. package/plugins/kimi-codex/dist/mcp/job-control.js +345 -0
  60. package/plugins/kimi-codex/dist/mcp/launcher.js +257 -0
  61. package/plugins/kimi-codex/dist/mcp/operations.js +301 -0
  62. package/plugins/kimi-codex/dist/mcp/protocol.js +275 -0
  63. package/plugins/kimi-codex/dist/mcp/server.js +8 -0
  64. package/plugins/kimi-codex/dist/mcp/stdio.js +78 -0
  65. package/plugins/kimi-codex/dist/mcp/supervised-child.js +15 -0
  66. package/plugins/kimi-codex/dist/mcp/tool-result.js +7 -0
  67. package/plugins/kimi-codex/dist/mcp/workspace.js +34 -0
  68. package/plugins/kimi-codex/dist/model-command.js +32 -0
  69. package/plugins/kimi-codex/dist/process-tree.js +48 -17
  70. package/plugins/kimi-codex/dist/swarm-activation.js +812 -0
  71. package/plugins/kimi-codex/dist/version.js +1 -1
@@ -0,0 +1,7 @@
1
+ export function textResult(text, structuredContent, isError = false) {
2
+ return {
3
+ content: [{ type: "text", text }],
4
+ ...(structuredContent ? { structuredContent } : {}),
5
+ isError,
6
+ };
7
+ }
@@ -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
+ }
@@ -6,7 +6,7 @@ const MAX_DESCENDANT_DEPTH = 8;
6
6
  const MAX_DESCENDANT_PIDS = 512;
7
7
  const PROCESS_EXIT_POLL_MS = 10;
8
8
  const PROCESS_SNAPSHOT_TIMEOUT_MS = 1_000;
9
- const PROCESS_IDENTITY_PROBE_TIMEOUT_MS = 250;
9
+ const PROCESS_IDENTITY_PROBE_TIMEOUT_MS = 1_000;
10
10
  const PROBE_TIMEOUT = Symbol("process-probe-timeout");
11
11
  /**
12
12
  * Capture descendant ancestry and reusable identities in the same bounded
@@ -23,6 +23,18 @@ export async function collectDescendantIdentities(rootPid) {
23
23
  }
24
24
  return snapshot;
25
25
  }
26
+ /** Return current members of the selected process groups, or undefined when unreadable. */
27
+ export async function collectProcessGroupsIdentities(processGroupIds) {
28
+ if (process.platform === "win32")
29
+ return undefined;
30
+ const groups = new Set(processGroupIds.filter((id) => Number.isInteger(id) && id > 0));
31
+ if (groups.size === 0)
32
+ return [];
33
+ const rows = await raceValueWithTimeout(collectProcessSnapshotRows(), PROCESS_SNAPSHOT_TIMEOUT_MS);
34
+ if (rows === PROBE_TIMEOUT || rows === undefined)
35
+ return undefined;
36
+ return rows.filter((row) => groups.has(row.processGroupId)).map(toProcessIdentity);
37
+ }
26
38
  /**
27
39
  * Compatibility surface for callers that only need numeric process listings.
28
40
  * Cancellation must use collectDescendantIdentities so ancestry and identity
@@ -36,15 +48,21 @@ export async function collectDescendants(rootPid) {
36
48
  return [];
37
49
  }
38
50
  }
39
- /** Read one reusable process identity. PID alone is not stable across teardown. */
40
- export async function readProcessIdentity(pid) {
51
+ /** Distinguish a missing process from a failed or timed-out identity probe. */
52
+ export async function probeProcessIdentity(pid) {
41
53
  if (!Number.isInteger(pid) || pid <= 0 || process.platform === "win32")
42
- return undefined;
54
+ return { complete: true };
43
55
  const read = process.platform === "linux"
44
56
  ? readProcessIdentityBatchFromProc([pid])
45
57
  : readProcessIdentityBatchWithPs([pid]);
46
58
  const batch = await raceValueWithTimeout(read, PROCESS_IDENTITY_PROBE_TIMEOUT_MS);
47
- return batch === PROBE_TIMEOUT ? undefined : batch.identities.get(pid);
59
+ if (batch === PROBE_TIMEOUT)
60
+ return { complete: false };
61
+ return { identity: batch.identities.get(pid), complete: batch.complete };
62
+ }
63
+ /** Read one reusable process identity. PID alone is not stable across teardown. */
64
+ export async function readProcessIdentity(pid) {
65
+ return (await probeProcessIdentity(pid)).identity;
48
66
  }
49
67
  /**
50
68
  * Revalidate all captured identities concurrently under one deadline. The
@@ -63,16 +81,26 @@ export async function revalidateProcessIdentities(expectedIdentities, options =
63
81
  if (batch === PROBE_TIMEOUT)
64
82
  return { identities: [], complete: false };
65
83
  const identities = [];
84
+ const groupChanged = [];
66
85
  for (const expectedIdentity of expected) {
67
86
  const current = batch.identities.get(expectedIdentity.pid);
68
- if (current !== undefined &&
69
- current.startToken === expectedIdentity.startToken &&
70
- current.processGroupId === expectedIdentity.processGroupId &&
71
- !isQuiescentState(current.state)) {
87
+ if (current === undefined ||
88
+ current.startToken !== expectedIdentity.startToken ||
89
+ isQuiescentState(current.state)) {
90
+ continue;
91
+ }
92
+ if (current.processGroupId !== expectedIdentity.processGroupId) {
93
+ groupChanged.push(current);
94
+ }
95
+ else {
72
96
  identities.push(current);
73
97
  }
74
98
  }
75
- return { identities, complete: batch.complete };
99
+ return {
100
+ identities,
101
+ complete: batch.complete,
102
+ ...(groupChanged.length > 0 ? { groupChanged } : {}),
103
+ };
76
104
  }
77
105
  /** Return the current identity only when pid, start token, and PGID still match. */
78
106
  export async function revalidateProcessIdentity(expected, identityReader) {
@@ -104,7 +132,7 @@ export async function waitForProcessTreeExit(identities, timeoutMs, options = {}
104
132
  identityReader: options.identityReader,
105
133
  timeoutMs: probeTimeoutMs,
106
134
  });
107
- active = !validation.complete || validation.identities.length > 0;
135
+ active = !validation.complete || validation.identities.length > 0 || (validation.groupChanged?.length ?? 0) > 0;
108
136
  }
109
137
  if (!active)
110
138
  return true;
@@ -117,13 +145,13 @@ export async function waitForProcessTreeExit(identities, timeoutMs, options = {}
117
145
  async function collectVerifiedDescendantIdentities(rootPid) {
118
146
  const initialRows = await collectProcessSnapshotRows();
119
147
  if (initialRows === undefined)
120
- return [];
148
+ throw new Error("process snapshot unavailable");
121
149
  const candidateRows = descendantRowsFromSnapshot(rootPid, initialRows);
122
150
  const initialRoot = initialRows.find((row) => row.pid === rootPid);
123
151
  if (initialRoot === undefined || candidateRows.length === 0)
124
152
  return [];
125
- // A second, targeted snapshot closes the cross-row /proc race: every child
126
- // identity and every numeric parent edge must still match before acceptance.
153
+ // A second, targeted snapshot closes the cross-row /proc race: each child
154
+ // must retain its PID and start token. Reparenting and PGID changes are not exit.
127
155
  const expectedRows = [initialRoot, ...candidateRows];
128
156
  const expectedByPid = new Map(expectedRows.map((row) => [row.pid, row]));
129
157
  const verificationRows = await readCurrentSnapshotRows(expectedRows);
@@ -131,11 +159,9 @@ async function collectVerifiedDescendantIdentities(rootPid) {
131
159
  const expected = expectedByPid.get(current.pid);
132
160
  return (expected !== undefined &&
133
161
  current.startToken === expected.startToken &&
134
- current.processGroupId === expected.processGroupId &&
135
- current.parentPid === expected.parentPid &&
136
162
  !isQuiescentState(current.state));
137
163
  });
138
- return descendantRowsFromSnapshot(rootPid, stableRows).map(toProcessIdentity);
164
+ return stableRows.map(toProcessIdentity);
139
165
  }
140
166
  function descendantRowsFromSnapshot(rootPid, rows) {
141
167
  const root = rows.find((row) => row.pid === rootPid);
@@ -186,6 +212,9 @@ async function readCurrentSnapshotRows(expectedRows) {
186
212
  Promise.all(procPids.map((pid) => readProcRow(pid))),
187
213
  collectProcessSnapshotRowsFromPs(psPids),
188
214
  ]);
215
+ if (procOutcomes.some((outcome) => outcome.status === "error") || (psPids.length > 0 && psRows === undefined)) {
216
+ throw new Error("process identity verification failed");
217
+ }
189
218
  return [
190
219
  ...procOutcomes.flatMap((outcome) => outcome.status === "found" ? [outcome.row] : []),
191
220
  ...(psRows ?? []),
@@ -212,6 +241,8 @@ async function collectProcessSnapshotRowsFromProc() {
212
241
  const outcomes = await Promise.all(entries
213
242
  .filter((entry) => /^\d+$/.test(entry))
214
243
  .map((entry) => readProcRow(Number.parseInt(entry, 10))));
244
+ if (outcomes.some((outcome) => outcome.status === "error"))
245
+ return undefined;
215
246
  return outcomes.flatMap((outcome) => outcome.status === "found" ? [outcome.row] : []);
216
247
  }
217
248
  async function collectProcessSnapshotRowsFromPs(pids) {