kimi-plugin-cc 2.4.5 → 2.5.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 (109) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +42 -26
  5. package/dist/dsh/companion.js +174 -0
  6. package/dist/dsh/index.js +156 -0
  7. package/dist/hooks/install-paths.js +2 -2
  8. package/dist/version.js +1 -1
  9. package/docs/models.md +5 -1
  10. package/docs/registry-releases.md +20 -17
  11. package/docs/safety.md +22 -9
  12. package/docs/upstream-2.4.1-certification.md +6 -0
  13. package/package.json +40 -6
  14. package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
  15. package/plugins/kimi-codex/dist/dsh/companion.js +174 -0
  16. package/plugins/kimi-codex/dist/dsh/index.js +156 -0
  17. package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
  18. package/plugins/kimi-codex/dist/version.js +1 -1
  19. package/plugins/kimi-dsh/LICENSE +201 -0
  20. package/plugins/kimi-dsh/README.md +80 -0
  21. package/plugins/kimi-dsh/cordis.patch.yml +45 -0
  22. package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
  23. package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
  24. package/plugins/kimi-dsh/dist/cli-client.js +876 -0
  25. package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
  26. package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
  27. package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
  28. package/plugins/kimi-dsh/dist/commands/models.js +140 -0
  29. package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
  30. package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
  31. package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
  32. package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
  33. package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
  34. package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
  35. package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
  36. package/plugins/kimi-dsh/dist/commands/result.js +66 -0
  37. package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
  38. package/plugins/kimi-dsh/dist/commands/review.js +220 -0
  39. package/plugins/kimi-dsh/dist/commands/setup.js +1047 -0
  40. package/plugins/kimi-dsh/dist/commands/status.js +27 -0
  41. package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
  42. package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
  43. package/plugins/kimi-dsh/dist/companion.js +97 -0
  44. package/plugins/kimi-dsh/dist/config.js +22 -0
  45. package/plugins/kimi-dsh/dist/dsh/companion.js +174 -0
  46. package/plugins/kimi-dsh/dist/dsh/index.js +156 -0
  47. package/plugins/kimi-dsh/dist/errors.js +40 -0
  48. package/plugins/kimi-dsh/dist/git.js +224 -0
  49. package/plugins/kimi-dsh/dist/help.js +43 -0
  50. package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
  51. package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
  52. package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
  53. package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
  54. package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
  55. package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
  56. package/plugins/kimi-dsh/dist/hooks/managed-block.js +619 -0
  57. package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
  58. package/plugins/kimi-dsh/dist/job-id.js +12 -0
  59. package/plugins/kimi-dsh/dist/job-store.js +659 -0
  60. package/plugins/kimi-dsh/dist/jobs.js +217 -0
  61. package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
  62. package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
  63. package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
  64. package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
  65. package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
  66. package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
  67. package/plugins/kimi-dsh/dist/logging.js +22 -0
  68. package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
  69. package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
  70. package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
  71. package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
  72. package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
  73. package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
  74. package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
  75. package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
  76. package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
  77. package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
  78. package/plugins/kimi-dsh/dist/model-command.js +32 -0
  79. package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
  80. package/plugins/kimi-dsh/dist/package.json +1 -0
  81. package/plugins/kimi-dsh/dist/parsing.js +620 -0
  82. package/plugins/kimi-dsh/dist/paths.js +27 -0
  83. package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
  84. package/plugins/kimi-dsh/dist/process-tree.js +479 -0
  85. package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
  86. package/plugins/kimi-dsh/dist/render.js +177 -0
  87. package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
  88. package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
  89. package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
  90. package/plugins/kimi-dsh/dist/session-title.js +293 -0
  91. package/plugins/kimi-dsh/dist/stream-json.js +346 -0
  92. package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
  93. package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
  94. package/plugins/kimi-dsh/dist/types.js +1 -0
  95. package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
  96. package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
  97. package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
  98. package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
  99. package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
  100. package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
  101. package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
  102. package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
  103. package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
  104. package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
  105. package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
  106. package/plugins/kimi-dsh/dist/version.js +7 -0
  107. package/plugins/kimi-dsh/package.json +48 -0
  108. package/plugins/kimi-dsh/scripts/companion.sh +37 -0
  109. package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
@@ -0,0 +1,346 @@
1
+ // Stream-JSON parser for `kimi -p --output-format stream-json`.
2
+ //
3
+ // Canonical record shapes (verified against kimi-code source at
4
+ // apps/kimi-code/src/cli/prompt-render.ts — `PromptJsonWriter` emits one JSON
5
+ // object per line via `JSON.stringify(message) + '\n'`):
6
+ //
7
+ // {"role":"assistant","content":"..."}
8
+ // {"role":"assistant","content":"...","tool_calls":[{type,id,function:{name,arguments}}]}
9
+ // {"role":"assistant","tool_calls":[...]}
10
+ // {"role":"tool","tool_call_id":"...","content":"..."}
11
+ // {"role":"meta","type":"session.resume_hint","session_id":"session_<uuid>","command":"kimi -r session_<uuid>","content":"To resume this session: kimi -r session_<uuid>"}
12
+ // {"role":"meta","type":"turn.step.retrying","failed_attempt":1,"next_attempt":2,"max_attempts":3,"delay_ms":300,"error_name":"APIProviderRateLimitError","error_message":"...","status_code":429}
13
+ // {"role":"meta","type":"system.version","version":"0.24.2"} // native-v2 marker; required first on native-v2 plans and filtered after capture
14
+ //
15
+ // {"type":"error","code":"session.occupied","message":"...","details":{...}} // 2.3.0; stdout-only refusal, exit 4
16
+ //
17
+ // Notes on what does and does not appear here:
18
+ // - session.resume_hint is emitted once at session end. Legacy 0.1.x sent
19
+ // the resume hint to stderr; stream-json uses this stdout meta record.
20
+ // - hook.result events arrive as role:"assistant" with the block rendered as
21
+ // plain text (see formatHookResultPlain in run-prompt.ts). Our
22
+ // PreToolUse hook's deny reason therefore surfaces in the assistant
23
+ // stream, not on a separate channel.
24
+ // - thinking.delta events are silently discarded by the CLI's
25
+ // PromptJsonWriter (`writeThinkingDelta(): void {}`).
26
+ // - tool.progress events are written to stderr only, never entering
27
+ // stream-json.
28
+ // - assistant messages are only emitted when content OR tool_calls are
29
+ // non-empty; we still defend against the empty case for resilience.
30
+ export const MAX_STREAM_JSON_LINE_BYTES = 1_048_576;
31
+ /**
32
+ * Stateful line-buffer parser. Hold partial-line bytes across chunk boundaries.
33
+ *
34
+ * Lifecycle:
35
+ * - `push(chunk)` — consume bytes; returns outcomes for any complete lines
36
+ * that finished within this chunk.
37
+ * - `flush()` — at end of stream, parse a valid trailing JSON record even
38
+ * without a final newline; malformed trailing input becomes diagnostic.
39
+ *
40
+ * Outcomes are non-throwing — malformed lines surface via `malformedLine` /
41
+ * `malformedReason` so callers can route them to a diagnostics log without
42
+ * crashing the parser. Blank lines (between records, or trailing whitespace)
43
+ * are skipped silently.
44
+ */
45
+ export class StreamJsonParser {
46
+ buffer = "";
47
+ push(chunk) {
48
+ this.buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
49
+ const outcomes = this.drainCompleteLines();
50
+ if (this.buffer.indexOf("\n") === -1 &&
51
+ Buffer.byteLength(this.buffer, "utf8") > MAX_STREAM_JSON_LINE_BYTES) {
52
+ outcomes.push({
53
+ malformedLine: makeOversizePreview(this.buffer),
54
+ malformedReason: `stream-json line exceeded ${MAX_STREAM_JSON_LINE_BYTES} bytes without newline`,
55
+ });
56
+ this.buffer = "";
57
+ }
58
+ return outcomes;
59
+ }
60
+ flush() {
61
+ const remainder = this.buffer;
62
+ this.buffer = "";
63
+ if (remainder.length === 0)
64
+ return [];
65
+ if (remainder.replace(/\r/g, "").length === 0)
66
+ return [];
67
+ return [parseLine(remainder)];
68
+ }
69
+ drainCompleteLines() {
70
+ const out = [];
71
+ let newlineIndex = this.buffer.indexOf("\n");
72
+ while (newlineIndex !== -1) {
73
+ const line = this.buffer.slice(0, newlineIndex);
74
+ this.buffer = this.buffer.slice(newlineIndex + 1);
75
+ if (line.replace(/\r/g, "").length > 0) {
76
+ out.push(parseLine(line));
77
+ }
78
+ newlineIndex = this.buffer.indexOf("\n");
79
+ }
80
+ return out;
81
+ }
82
+ }
83
+ function parseLine(line) {
84
+ const cleaned = line.endsWith("\r") ? line.slice(0, -1) : line;
85
+ let parsed;
86
+ try {
87
+ parsed = JSON.parse(cleaned);
88
+ }
89
+ catch (err) {
90
+ return {
91
+ malformedLine: line,
92
+ malformedReason: `json parse: ${err.message}`,
93
+ };
94
+ }
95
+ if (!isRecord(parsed)) {
96
+ return { malformedLine: line, malformedReason: "not a JSON object" };
97
+ }
98
+ const role = parsed["role"];
99
+ if (role === "assistant") {
100
+ return validateAssistant(parsed, line);
101
+ }
102
+ if (role === "tool") {
103
+ return validateToolResult(parsed, line);
104
+ }
105
+ if (role === "meta") {
106
+ return validateMeta(parsed, line);
107
+ }
108
+ if (role === undefined && parsed["type"] === "goal.summary") {
109
+ return validateGoalSummary(parsed, line);
110
+ }
111
+ if (role === undefined && parsed["type"] === "error") {
112
+ if (typeof parsed["code"] !== "string" || parsed["code"].length === 0 ||
113
+ typeof parsed["message"] !== "string" || parsed["message"].length === 0 ||
114
+ (parsed["details"] !== undefined && !isRecord(parsed["details"]))) {
115
+ return { malformedLine: line, malformedReason: "error record field has unexpected type" };
116
+ }
117
+ // Keep bounded diagnostic fields only; holder metadata is not needed and
118
+ // must not become a session-id source or be copied into persisted errors.
119
+ return { cliError: {
120
+ type: "error",
121
+ code: parsed["code"].slice(0, 200),
122
+ message: parsed["message"].slice(0, 4000),
123
+ } };
124
+ }
125
+ if (typeof role === "string" && role.length > 0) {
126
+ return { unknownRecord: { role, raw: parsed } };
127
+ }
128
+ return {
129
+ malformedLine: line,
130
+ malformedReason: `unknown role: ${JSON.stringify(role)}`,
131
+ };
132
+ }
133
+ function validateGoalSummary(parsed, line) {
134
+ // Each field is `T | null` upstream (null when no goal snapshot exists). A
135
+ // missing field is tolerated as null; a present-but-wrong-typed field is
136
+ // malformed (so a shape change surfaces in diagnostics rather than silently
137
+ // coercing). Mirrors the defensive posture of validateMeta.
138
+ const asStringOrNull = (v) => v === undefined || v === null ? null : typeof v === "string" ? v : undefined;
139
+ const asNumberOrNull = (v) => v === undefined || v === null
140
+ ? null
141
+ : typeof v === "number" && Number.isFinite(v)
142
+ ? v
143
+ : undefined;
144
+ const goalId = asStringOrNull(parsed["goalId"]);
145
+ const status = asStringOrNull(parsed["status"]);
146
+ const reason = asStringOrNull(parsed["reason"]);
147
+ const turnsUsed = asNumberOrNull(parsed["turnsUsed"]);
148
+ const tokensUsed = asNumberOrNull(parsed["tokensUsed"]);
149
+ const wallClockMs = asNumberOrNull(parsed["wallClockMs"]);
150
+ if (goalId === undefined ||
151
+ status === undefined ||
152
+ reason === undefined ||
153
+ turnsUsed === undefined ||
154
+ tokensUsed === undefined ||
155
+ wallClockMs === undefined) {
156
+ return { malformedLine: line, malformedReason: "goal.summary field has unexpected type" };
157
+ }
158
+ const goalSummary = {
159
+ type: "goal.summary",
160
+ goalId,
161
+ status,
162
+ reason,
163
+ turnsUsed,
164
+ tokensUsed,
165
+ wallClockMs,
166
+ };
167
+ return { goalSummary };
168
+ }
169
+ function validateMeta(parsed, line) {
170
+ const type = parsed["type"];
171
+ if (type === "session.resume_hint") {
172
+ const sessionId = parsed["session_id"];
173
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
174
+ return {
175
+ malformedLine: line,
176
+ malformedReason: "meta.session.resume_hint.session_id not non-empty string",
177
+ };
178
+ }
179
+ const record = {
180
+ role: "meta",
181
+ type: "session.resume_hint",
182
+ sessionId,
183
+ };
184
+ return { record };
185
+ }
186
+ if (type === "turn.step.retrying") {
187
+ const failedAttempt = parsed["failed_attempt"];
188
+ const nextAttempt = parsed["next_attempt"];
189
+ const maxAttempts = parsed["max_attempts"];
190
+ const delayMs = parsed["delay_ms"];
191
+ const errorName = parsed["error_name"];
192
+ const errorMessage = parsed["error_message"];
193
+ const statusCode = parsed["status_code"];
194
+ if (typeof failedAttempt !== "number" ||
195
+ !Number.isFinite(failedAttempt) ||
196
+ typeof nextAttempt !== "number" ||
197
+ !Number.isFinite(nextAttempt) ||
198
+ typeof maxAttempts !== "number" ||
199
+ !Number.isFinite(maxAttempts) ||
200
+ typeof delayMs !== "number" ||
201
+ !Number.isFinite(delayMs) ||
202
+ typeof errorName !== "string" ||
203
+ typeof errorMessage !== "string" ||
204
+ (statusCode !== undefined &&
205
+ (typeof statusCode !== "number" || !Number.isFinite(statusCode)))) {
206
+ return {
207
+ malformedLine: line,
208
+ malformedReason: "meta.turn.step.retrying field has unexpected type",
209
+ };
210
+ }
211
+ const record = {
212
+ role: "meta",
213
+ type: "turn.step.retrying",
214
+ failedAttempt,
215
+ nextAttempt,
216
+ maxAttempts,
217
+ delayMs,
218
+ errorName,
219
+ errorMessage,
220
+ ...(typeof statusCode === "number" && { statusCode }),
221
+ };
222
+ return { record };
223
+ }
224
+ if (type === "system.version") {
225
+ const version = parsed["version"];
226
+ if (typeof version !== "string" || version.length === 0) {
227
+ return {
228
+ malformedLine: line,
229
+ malformedReason: "meta.system.version.version not non-empty string",
230
+ };
231
+ }
232
+ const record = {
233
+ role: "meta",
234
+ type: "system.version",
235
+ version,
236
+ };
237
+ return { record };
238
+ }
239
+ // Unknown meta types are diagnostic-only and nonfatal.
240
+ return {
241
+ malformedLine: line,
242
+ malformedReason: `unknown meta.type: ${JSON.stringify(type)}`,
243
+ };
244
+ }
245
+ function validateAssistant(parsed, line) {
246
+ const content = parsed["content"];
247
+ if (content !== undefined && typeof content !== "string") {
248
+ return { malformedLine: line, malformedReason: "assistant.content not string" };
249
+ }
250
+ const toolCallsRaw = parsed["tool_calls"];
251
+ let toolCalls;
252
+ if (toolCallsRaw !== undefined) {
253
+ if (!Array.isArray(toolCallsRaw)) {
254
+ return { malformedLine: line, malformedReason: "assistant.tool_calls not array" };
255
+ }
256
+ toolCalls = [];
257
+ for (const candidate of toolCallsRaw) {
258
+ const validated = validateToolCall(candidate);
259
+ if (validated === undefined) {
260
+ return {
261
+ malformedLine: line,
262
+ malformedReason: "assistant.tool_calls entry invalid",
263
+ };
264
+ }
265
+ toolCalls.push(validated);
266
+ }
267
+ }
268
+ if (content === undefined && (toolCalls === undefined || toolCalls.length === 0)) {
269
+ return {
270
+ malformedLine: line,
271
+ malformedReason: "assistant has neither content nor tool_calls",
272
+ };
273
+ }
274
+ const record = {
275
+ role: "assistant",
276
+ ...(content !== undefined && { content }),
277
+ ...(toolCalls !== undefined && { tool_calls: toolCalls }),
278
+ };
279
+ return { record };
280
+ }
281
+ function validateToolCall(candidate) {
282
+ if (!isRecord(candidate))
283
+ return undefined;
284
+ if (candidate["type"] !== "function")
285
+ return undefined;
286
+ const id = candidate["id"];
287
+ if (typeof id !== "string" || id.length === 0)
288
+ return undefined;
289
+ const fn = candidate["function"];
290
+ if (!isRecord(fn))
291
+ return undefined;
292
+ const name = fn["name"];
293
+ const args = fn["arguments"];
294
+ if (typeof name !== "string" || typeof args !== "string")
295
+ return undefined;
296
+ return { type: "function", id, function: { name, arguments: args } };
297
+ }
298
+ function validateToolResult(parsed, line) {
299
+ const toolCallId = parsed["tool_call_id"];
300
+ if (typeof toolCallId !== "string" || toolCallId.length === 0) {
301
+ return {
302
+ malformedLine: line,
303
+ malformedReason: "tool.tool_call_id not non-empty string",
304
+ };
305
+ }
306
+ const content = parsed["content"];
307
+ if (typeof content !== "string") {
308
+ return { malformedLine: line, malformedReason: "tool.content not string" };
309
+ }
310
+ const record = {
311
+ role: "tool",
312
+ tool_call_id: toolCallId,
313
+ content,
314
+ };
315
+ return { record };
316
+ }
317
+ function isRecord(value) {
318
+ return typeof value === "object" && value !== null && !Array.isArray(value);
319
+ }
320
+ /**
321
+ * Match kimi-code's stderr session announce line. In stream-json mode this
322
+ * is a 0.1.x compatibility fallback only — kimi 0.2.0+ emits the resume
323
+ * hint as a `role: meta, type: session.resume_hint` record on stdout (see
324
+ * SessionResumeHintRecord above and apps/kimi-code/src/cli/run-prompt.ts).
325
+ *
326
+ * The pattern accepts both 0.1.x bare-UUID format and 0.2.0+ `session_<uuid>`
327
+ * format so the fallback survives mixed-version environments. The captured
328
+ * token is whatever appears after `kimi -r ` — we treat it as an opaque
329
+ * session identifier and round-trip it verbatim via `kimi -r <token>`.
330
+ *
331
+ * Returns the captured session id, or undefined if no match.
332
+ */
333
+ export function extractSessionIdFromStderr(stderr) {
334
+ // The accepted shapes are tightly anchored — both alternations require
335
+ // a full UUID payload. The `session_` prefix is the 0.2.0+ form; the
336
+ // bare UUID is the 0.1.x form. Without the anchoring, a malformed or
337
+ // hostile stderr line could pin `session_--------` (8 dashes) or any
338
+ // 8+ char hex-dash token as the captured session id, weakening the
339
+ // "anchored full-UUID regex" safety invariant documented in AGENTS.md.
340
+ const uuid = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
341
+ const pattern = new RegExp(`^To resume this session:\\s+kimi\\s+-r\\s+(session_${uuid}|${uuid})\\s*$`, "im");
342
+ return stderr.match(pattern)?.[1];
343
+ }
344
+ function makeOversizePreview(line) {
345
+ return `${line.slice(0, 200)}[truncated]`;
346
+ }