context-mode 1.0.150 → 1.0.152
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 +1 -1
- package/.codex-plugin/mcp.json +5 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +16 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +89 -3
- package/build/adapters/claude-code/hooks.js +2 -2
- package/build/adapters/claude-code/index.js +14 -13
- package/build/adapters/client-map.js +3 -0
- package/build/adapters/detect.js +13 -1
- package/build/adapters/gemini-cli/hooks.d.ts +10 -0
- package/build/adapters/gemini-cli/hooks.js +12 -2
- package/build/adapters/gemini-cli/index.d.ts +21 -1
- package/build/adapters/gemini-cli/index.js +37 -1
- package/build/adapters/kimi/config.d.ts +8 -0
- package/build/adapters/kimi/config.js +8 -0
- package/build/adapters/kimi/hooks.d.ts +28 -0
- package/build/adapters/kimi/hooks.js +34 -0
- package/build/adapters/kimi/index.d.ts +66 -0
- package/build/adapters/kimi/index.js +537 -0
- package/build/adapters/kimi/paths.d.ts +1 -0
- package/build/adapters/kimi/paths.js +12 -0
- package/build/adapters/kiro/hooks.js +2 -2
- package/build/adapters/openclaw/plugin.d.ts +14 -13
- package/build/adapters/openclaw/plugin.js +140 -40
- package/build/adapters/opencode/plugin.js +4 -3
- package/build/adapters/opencode/zod3tov4.js +8 -8
- package/build/adapters/pi/extension.js +9 -24
- package/build/adapters/pi/mcp-bridge.js +37 -0
- package/build/adapters/qwen-code/index.js +7 -7
- package/build/adapters/types.d.ts +39 -2
- package/build/adapters/types.js +55 -2
- package/build/adapters/vscode-copilot/index.js +13 -1
- package/build/cli.js +433 -25
- package/build/executor.js +6 -3
- package/build/runtime.d.ts +81 -1
- package/build/runtime.js +195 -9
- package/build/search/ctx-search-schema.d.ts +90 -0
- package/build/search/ctx-search-schema.js +135 -0
- package/build/search/unified.d.ts +12 -0
- package/build/search/unified.js +17 -2
- package/build/server.d.ts +2 -1
- package/build/server.js +378 -97
- package/build/session/analytics.d.ts +36 -13
- package/build/session/analytics.js +123 -26
- package/build/session/db.d.ts +24 -0
- package/build/session/db.js +41 -0
- package/build/session/extract.js +30 -0
- package/build/session/snapshot.js +24 -0
- package/build/store.d.ts +12 -1
- package/build/store.js +72 -20
- package/build/types.d.ts +7 -0
- package/build/util/project-dir.d.ts +19 -16
- package/build/util/project-dir.js +80 -45
- package/cli.bundle.mjs +371 -320
- package/configs/kimi/hooks.json +54 -0
- package/configs/pi/AGENTS.md +3 -85
- package/hooks/cache-heal-utils.mjs +148 -0
- package/hooks/core/formatters.mjs +26 -0
- package/hooks/core/routing.mjs +9 -1
- package/hooks/core/stdin.mjs +74 -3
- package/hooks/core/tool-naming.mjs +1 -0
- package/hooks/heal-partial-install.mjs +712 -0
- package/hooks/kimi/platform.mjs +1 -0
- package/hooks/kimi/posttooluse.mjs +72 -0
- package/hooks/kimi/precompact.mjs +80 -0
- package/hooks/kimi/pretooluse.mjs +42 -0
- package/hooks/kimi/sessionend.mjs +61 -0
- package/hooks/kimi/sessionstart.mjs +113 -0
- package/hooks/kimi/stop.mjs +61 -0
- package/hooks/kimi/userpromptsubmit.mjs +90 -0
- package/hooks/normalize-hooks.mjs +66 -12
- package/hooks/routing-block.mjs +8 -2
- package/hooks/security.bundle.mjs +1 -1
- package/hooks/session-db.bundle.mjs +6 -4
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/session-helpers.mjs +93 -3
- package/hooks/session-snapshot.bundle.mjs +20 -19
- package/hooks/sessionstart.mjs +64 -0
- package/insight/server.mjs +15 -3
- package/openclaw.plugin.json +16 -1
- package/package.json +1 -1
- package/scripts/heal-installed-plugins.mjs +31 -10
- package/scripts/postinstall.mjs +10 -0
- package/server.bundle.mjs +206 -157
- package/skills/ctx-index/SKILL.md +46 -0
- package/skills/ctx-search/SKILL.md +35 -0
- package/start.mjs +84 -11
- package/build/cache-heal.d.ts +0 -48
- package/build/cache-heal.js +0 -150
- package/build/concurrency/runPool.d.ts +0 -36
- package/build/concurrency/runPool.js +0 -51
- package/build/openclaw/mcp-tools.d.ts +0 -54
- package/build/openclaw/mcp-tools.js +0 -198
- package/build/openclaw/workspace-router.d.ts +0 -29
- package/build/openclaw/workspace-router.js +0 -64
- package/build/openclaw-plugin.d.ts +0 -130
- package/build/openclaw-plugin.js +0 -626
- package/build/opencode-plugin.d.ts +0 -122
- package/build/opencode-plugin.js +0 -375
- package/build/pi-extension.d.ts +0 -14
- package/build/pi-extension.js +0 -451
- package/build/routing-block.d.ts +0 -8
- package/build/routing-block.js +0 -86
- package/build/tool-naming.d.ts +0 -4
- package/build/tool-naming.js +0 -24
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/kimi — Kimi Code CLI platform adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements HookAdapter for Kimi Code CLI's JSON stdin/stdout paradigm.
|
|
5
|
+
*
|
|
6
|
+
* Kimi Code CLI hook specifics:
|
|
7
|
+
* - 7 hook events: PreToolUse, PostToolUse, PreCompact, SessionStart,
|
|
8
|
+
* SessionEnd, UserPromptSubmit, Stop
|
|
9
|
+
* - Same wire protocol as Codex CLI (JSON stdin → stdout)
|
|
10
|
+
* - Config: $KIMI_CODE_HOME or ~/.kimi-code (config.toml + mcp.json)
|
|
11
|
+
* - Hooks are inline `[[hooks]]` array tables in config.toml
|
|
12
|
+
* - Session dir: $KIMI_CODE_HOME/context-mode/sessions/
|
|
13
|
+
*
|
|
14
|
+
* PreToolUse is deny-only — ask / modify / additionalContext are silently
|
|
15
|
+
* dropped by the host runner (verified upstream at runner.ts:36-39,162-178).
|
|
16
|
+
*/
|
|
17
|
+
import { BaseAdapter } from "../base.js";
|
|
18
|
+
import { type HookAdapter, type HookParadigm, type PlatformCapabilities, type DiagnosticResult, type PreToolUseEvent, type PostToolUseEvent, type PreCompactEvent, type SessionStartEvent, type PreToolUseResponse, type PostToolUseResponse, type PreCompactResponse, type SessionStartResponse, type HookRegistration } from "../types.js";
|
|
19
|
+
type KimiVersionRunner = (file: string, args: string[], options: {
|
|
20
|
+
encoding: BufferEncoding;
|
|
21
|
+
stdio: ["ignore", "pipe", "ignore"];
|
|
22
|
+
timeout: number;
|
|
23
|
+
}) => string | Buffer;
|
|
24
|
+
export declare function probeKimiCliVersion(runCommand?: KimiVersionRunner): string | null;
|
|
25
|
+
export declare class KimiAdapter extends BaseAdapter implements HookAdapter {
|
|
26
|
+
constructor();
|
|
27
|
+
readonly name = "Kimi Code CLI";
|
|
28
|
+
readonly paradigm: HookParadigm;
|
|
29
|
+
readonly capabilities: PlatformCapabilities;
|
|
30
|
+
parsePreToolUseInput(raw: unknown): PreToolUseEvent;
|
|
31
|
+
parsePostToolUseInput(raw: unknown): PostToolUseEvent;
|
|
32
|
+
parsePreCompactInput(raw: unknown): PreCompactEvent;
|
|
33
|
+
parseSessionStartInput(raw: unknown): SessionStartEvent;
|
|
34
|
+
formatPreToolUseResponse(response: PreToolUseResponse): unknown;
|
|
35
|
+
formatPostToolUseResponse(response: PostToolUseResponse): unknown;
|
|
36
|
+
formatPreCompactResponse(_response: PreCompactResponse): unknown;
|
|
37
|
+
formatSessionStartResponse(response: SessionStartResponse): unknown;
|
|
38
|
+
getConfigDir(_projectDir?: string): string;
|
|
39
|
+
getSettingsPath(): string;
|
|
40
|
+
getMcpPath(): string;
|
|
41
|
+
getSessionDir(): string;
|
|
42
|
+
getInstructionFiles(): string[];
|
|
43
|
+
getMemoryDir(projectDir?: string): string;
|
|
44
|
+
generateHookConfig(_pluginRoot: string): HookRegistration;
|
|
45
|
+
readSettings(): Record<string, unknown> | null;
|
|
46
|
+
writeSettings(_settings: Record<string, unknown>): void;
|
|
47
|
+
validateHooks(_pluginRoot: string): DiagnosticResult[];
|
|
48
|
+
checkPluginRegistration(): DiagnosticResult;
|
|
49
|
+
getInstalledVersion(): string;
|
|
50
|
+
configureAllHooks(_pluginRoot: string): string[];
|
|
51
|
+
backupSettings(): string | null;
|
|
52
|
+
setHookPermissions(_pluginRoot: string): string[];
|
|
53
|
+
updatePluginRegistry(_pluginRoot: string, _version: string): void;
|
|
54
|
+
getRoutingInstructions(): string;
|
|
55
|
+
private getProjectDir;
|
|
56
|
+
private extractSessionId;
|
|
57
|
+
private backupFile;
|
|
58
|
+
private isExpectedHookEntry;
|
|
59
|
+
private entryContainsManagedCommand;
|
|
60
|
+
/**
|
|
61
|
+
* Rebuild config.toml by removing old managed hooks and appending new ones.
|
|
62
|
+
* Preserves everything outside [[hooks]] blocks and all non-managed hooks.
|
|
63
|
+
*/
|
|
64
|
+
private rebuildToml;
|
|
65
|
+
}
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/kimi — Kimi Code CLI platform adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements HookAdapter for Kimi Code CLI's JSON stdin/stdout paradigm.
|
|
5
|
+
*
|
|
6
|
+
* Kimi Code CLI hook specifics:
|
|
7
|
+
* - 7 hook events: PreToolUse, PostToolUse, PreCompact, SessionStart,
|
|
8
|
+
* SessionEnd, UserPromptSubmit, Stop
|
|
9
|
+
* - Same wire protocol as Codex CLI (JSON stdin → stdout)
|
|
10
|
+
* - Config: $KIMI_CODE_HOME or ~/.kimi-code (config.toml + mcp.json)
|
|
11
|
+
* - Hooks are inline `[[hooks]]` array tables in config.toml
|
|
12
|
+
* - Session dir: $KIMI_CODE_HOME/context-mode/sessions/
|
|
13
|
+
*
|
|
14
|
+
* PreToolUse is deny-only — ask / modify / additionalContext are silently
|
|
15
|
+
* dropped by the host runner (verified upstream at runner.ts:36-39,162-178).
|
|
16
|
+
*/
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { readFileSync, writeFileSync, accessSync, copyFileSync, constants, mkdirSync, } from "node:fs";
|
|
19
|
+
import { resolve, dirname, join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { BaseAdapter, resolveContextModeDataRoot } from "../base.js";
|
|
22
|
+
import { hashProjectDirCanonical } from "../../session/db.js";
|
|
23
|
+
import { resolveKimiConfigDir } from "./paths.js";
|
|
24
|
+
// ─────────────────────────────────────────────────────────
|
|
25
|
+
// Constants
|
|
26
|
+
// ─────────────────────────────────────────────────────────
|
|
27
|
+
// PreToolUse matcher: canonical tool names + context-mode bare MCP tool
|
|
28
|
+
// names + external MCP catch-all literal. Same charset-clean restriction
|
|
29
|
+
// as Codex (Rust regex, no look-around).
|
|
30
|
+
const PRE_TOOL_USE_MATCHER_PATTERN = "Bash|Shell|Read|Edit|Write|WebFetch|Agent|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__";
|
|
31
|
+
const KIMI_HOOK_COMMANDS = {
|
|
32
|
+
PreToolUse: "context-mode hook kimi pretooluse",
|
|
33
|
+
PostToolUse: "context-mode hook kimi posttooluse",
|
|
34
|
+
SessionStart: "context-mode hook kimi sessionstart",
|
|
35
|
+
SessionEnd: "context-mode hook kimi sessionend",
|
|
36
|
+
PreCompact: "context-mode hook kimi precompact",
|
|
37
|
+
UserPromptSubmit: "context-mode hook kimi userpromptsubmit",
|
|
38
|
+
Stop: "context-mode hook kimi stop",
|
|
39
|
+
};
|
|
40
|
+
const LEGACY_HOOK_PATH_SUFFIXES = {
|
|
41
|
+
PreToolUse: ["hooks/pretooluse.mjs", "hooks/kimi/pretooluse.mjs"],
|
|
42
|
+
PostToolUse: ["hooks/posttooluse.mjs", "hooks/kimi/posttooluse.mjs"],
|
|
43
|
+
SessionStart: ["hooks/sessionstart.mjs", "hooks/kimi/sessionstart.mjs"],
|
|
44
|
+
SessionEnd: ["hooks/sessionend.mjs", "hooks/kimi/sessionend.mjs"],
|
|
45
|
+
PreCompact: ["hooks/precompact.mjs", "hooks/kimi/precompact.mjs"],
|
|
46
|
+
UserPromptSubmit: ["hooks/userpromptsubmit.mjs", "hooks/kimi/userpromptsubmit.mjs"],
|
|
47
|
+
Stop: ["hooks/stop.mjs", "hooks/kimi/stop.mjs"],
|
|
48
|
+
};
|
|
49
|
+
export function probeKimiCliVersion(runCommand = execFileSync) {
|
|
50
|
+
try {
|
|
51
|
+
const output = process.platform === "win32"
|
|
52
|
+
? runCommand("cmd.exe", ["/d", "/s", "/c", "kimi --version"], {
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
55
|
+
timeout: 5000,
|
|
56
|
+
})
|
|
57
|
+
: runCommand("kimi", ["--version"], {
|
|
58
|
+
encoding: "utf-8",
|
|
59
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
60
|
+
timeout: 1500,
|
|
61
|
+
});
|
|
62
|
+
const version = String(output).trim();
|
|
63
|
+
return version.length > 0 ? version : "available (version output empty)";
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ─────────────────────────────────────────────────────────
|
|
70
|
+
// TOML [[hooks]] helpers
|
|
71
|
+
// ─────────────────────────────────────────────────────────
|
|
72
|
+
function parseKimiHooks(toml) {
|
|
73
|
+
const hooks = [];
|
|
74
|
+
const lines = toml.split(/\r?\n/);
|
|
75
|
+
let current = null;
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
if (/^\s*\[\[hooks\]\]\s*(?:#.*)?$/.test(line)) {
|
|
78
|
+
if (current && current.event && current.command) {
|
|
79
|
+
hooks.push(current);
|
|
80
|
+
}
|
|
81
|
+
current = {};
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!current)
|
|
85
|
+
continue;
|
|
86
|
+
const kv = line.match(/^\s*(\w+)\s*=\s*(?:"([^"]*)"|(\d+))\s*(?:#.*)?$/);
|
|
87
|
+
if (kv) {
|
|
88
|
+
const key = kv[1];
|
|
89
|
+
const strVal = kv[2];
|
|
90
|
+
const numVal = kv[3];
|
|
91
|
+
if (strVal !== undefined) {
|
|
92
|
+
current[key] = strVal;
|
|
93
|
+
}
|
|
94
|
+
else if (numVal !== undefined) {
|
|
95
|
+
current[key] = Number(numVal);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (current && current.event && current.command) {
|
|
100
|
+
hooks.push(current);
|
|
101
|
+
}
|
|
102
|
+
return hooks;
|
|
103
|
+
}
|
|
104
|
+
function formatKimiHook(hook) {
|
|
105
|
+
const lines = ["[[hooks]]"];
|
|
106
|
+
lines.push(`event = "${hook.event}"`);
|
|
107
|
+
if (hook.matcher)
|
|
108
|
+
lines.push(`matcher = "${hook.matcher}"`);
|
|
109
|
+
lines.push(`command = "${hook.command}"`);
|
|
110
|
+
if (hook.timeout !== undefined)
|
|
111
|
+
lines.push(`timeout = ${hook.timeout}`);
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
function isManagedKimiHook(hook) {
|
|
115
|
+
return hook.command.includes("context-mode hook kimi");
|
|
116
|
+
}
|
|
117
|
+
function buildKimiHookFromRegistration(eventName, entry) {
|
|
118
|
+
const command = (entry.hooks?.[0]?.command ?? "").trim();
|
|
119
|
+
if (!command)
|
|
120
|
+
return null;
|
|
121
|
+
return {
|
|
122
|
+
event: eventName,
|
|
123
|
+
matcher: entry.matcher || undefined,
|
|
124
|
+
command,
|
|
125
|
+
timeout: 30,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// ─────────────────────────────────────────────────────────
|
|
129
|
+
// Adapter implementation
|
|
130
|
+
// ─────────────────────────────────────────────────────────
|
|
131
|
+
export class KimiAdapter extends BaseAdapter {
|
|
132
|
+
constructor() {
|
|
133
|
+
super([".kimi-code"]);
|
|
134
|
+
}
|
|
135
|
+
name = "Kimi Code CLI";
|
|
136
|
+
paradigm = "json-stdio";
|
|
137
|
+
capabilities = {
|
|
138
|
+
preToolUse: true,
|
|
139
|
+
postToolUse: true,
|
|
140
|
+
preCompact: true,
|
|
141
|
+
sessionStart: true,
|
|
142
|
+
canModifyArgs: false,
|
|
143
|
+
canModifyOutput: false,
|
|
144
|
+
canInjectSessionContext: false,
|
|
145
|
+
};
|
|
146
|
+
// ── Input parsing ──────────────────────────────────────
|
|
147
|
+
parsePreToolUseInput(raw) {
|
|
148
|
+
const input = raw;
|
|
149
|
+
return {
|
|
150
|
+
toolName: input.tool_name ?? "",
|
|
151
|
+
toolInput: input.tool_input ?? {},
|
|
152
|
+
sessionId: this.extractSessionId(input),
|
|
153
|
+
projectDir: this.getProjectDir(input),
|
|
154
|
+
raw,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
parsePostToolUseInput(raw) {
|
|
158
|
+
const input = raw;
|
|
159
|
+
return {
|
|
160
|
+
toolName: input.tool_name ?? "",
|
|
161
|
+
toolInput: input.tool_input ?? {},
|
|
162
|
+
toolOutput: input.tool_response,
|
|
163
|
+
sessionId: this.extractSessionId(input),
|
|
164
|
+
projectDir: this.getProjectDir(input),
|
|
165
|
+
raw,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
parsePreCompactInput(raw) {
|
|
169
|
+
const input = raw;
|
|
170
|
+
return {
|
|
171
|
+
sessionId: this.extractSessionId(input),
|
|
172
|
+
projectDir: this.getProjectDir(input),
|
|
173
|
+
raw,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
parseSessionStartInput(raw) {
|
|
177
|
+
const input = raw;
|
|
178
|
+
// Kimi Code emits ONLY 'startup' | 'resume' for SessionStart.source:
|
|
179
|
+
// refs/platforms/kimi-code/.../session/index.ts:153,181,495
|
|
180
|
+
const rawSource = input.source ?? "startup";
|
|
181
|
+
const source = rawSource === "resume" ? "resume" : "startup";
|
|
182
|
+
return {
|
|
183
|
+
sessionId: this.extractSessionId(input),
|
|
184
|
+
source,
|
|
185
|
+
projectDir: this.getProjectDir(input),
|
|
186
|
+
raw,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// ── Response formatting ────────────────────────────────
|
|
190
|
+
// Kimi Code uses hookSpecificOutput wrapper for all hook responses.
|
|
191
|
+
// Like Codex, Kimi only supports permissionDecision === "deny" in
|
|
192
|
+
// PreToolUse — ask / modify / additionalContext are silently dropped.
|
|
193
|
+
formatPreToolUseResponse(response) {
|
|
194
|
+
if (response.decision === "deny") {
|
|
195
|
+
return {
|
|
196
|
+
hookSpecificOutput: {
|
|
197
|
+
hookEventName: "PreToolUse",
|
|
198
|
+
permissionDecision: "deny",
|
|
199
|
+
permissionDecisionReason: response.reason ?? "Blocked by context-mode hook",
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// "allow" and everything else — return empty object for passthrough
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
formatPostToolUseResponse(response) {
|
|
207
|
+
if (response.additionalContext) {
|
|
208
|
+
return {
|
|
209
|
+
hookSpecificOutput: {
|
|
210
|
+
hookEventName: "PostToolUse",
|
|
211
|
+
additionalContext: response.additionalContext,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {};
|
|
216
|
+
}
|
|
217
|
+
formatPreCompactResponse(_response) {
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
formatSessionStartResponse(response) {
|
|
221
|
+
if (response.context) {
|
|
222
|
+
return {
|
|
223
|
+
hookSpecificOutput: {
|
|
224
|
+
hookEventName: "SessionStart",
|
|
225
|
+
additionalContext: response.context,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {};
|
|
230
|
+
}
|
|
231
|
+
// ── Configuration ──────────────────────────────────────
|
|
232
|
+
getConfigDir(_projectDir) {
|
|
233
|
+
return resolveKimiConfigDir();
|
|
234
|
+
}
|
|
235
|
+
getSettingsPath() {
|
|
236
|
+
return join(this.getConfigDir(), "config.toml");
|
|
237
|
+
}
|
|
238
|
+
getMcpPath() {
|
|
239
|
+
return join(this.getConfigDir(), "mcp.json");
|
|
240
|
+
}
|
|
241
|
+
getSessionDir() {
|
|
242
|
+
const override = resolveContextModeDataRoot();
|
|
243
|
+
const dir = override
|
|
244
|
+
? join(override, "context-mode", "sessions")
|
|
245
|
+
: join(this.getConfigDir(), "context-mode", "sessions");
|
|
246
|
+
mkdirSync(dir, { recursive: true });
|
|
247
|
+
return dir;
|
|
248
|
+
}
|
|
249
|
+
getInstructionFiles() {
|
|
250
|
+
return ["AGENTS.md", "AGENTS.override.md"];
|
|
251
|
+
}
|
|
252
|
+
getMemoryDir(projectDir) {
|
|
253
|
+
const override = resolveContextModeDataRoot();
|
|
254
|
+
const base = override
|
|
255
|
+
? join(override, "context-mode", "memory")
|
|
256
|
+
: join(this.getConfigDir(), "memory");
|
|
257
|
+
if (!projectDir)
|
|
258
|
+
return base;
|
|
259
|
+
return join(base, hashProjectDirCanonical(projectDir));
|
|
260
|
+
}
|
|
261
|
+
generateHookConfig(_pluginRoot) {
|
|
262
|
+
return {
|
|
263
|
+
PreToolUse: [
|
|
264
|
+
{
|
|
265
|
+
matcher: PRE_TOOL_USE_MATCHER_PATTERN,
|
|
266
|
+
hooks: [{ type: "command", command: KIMI_HOOK_COMMANDS.PreToolUse }],
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
PostToolUse: [
|
|
270
|
+
{ matcher: "", hooks: [{ type: "command", command: KIMI_HOOK_COMMANDS.PostToolUse }] },
|
|
271
|
+
],
|
|
272
|
+
SessionStart: [
|
|
273
|
+
{ matcher: "", hooks: [{ type: "command", command: KIMI_HOOK_COMMANDS.SessionStart }] },
|
|
274
|
+
],
|
|
275
|
+
SessionEnd: [
|
|
276
|
+
{ matcher: "", hooks: [{ type: "command", command: KIMI_HOOK_COMMANDS.SessionEnd }] },
|
|
277
|
+
],
|
|
278
|
+
PreCompact: [
|
|
279
|
+
{ matcher: "", hooks: [{ type: "command", command: KIMI_HOOK_COMMANDS.PreCompact }] },
|
|
280
|
+
],
|
|
281
|
+
UserPromptSubmit: [
|
|
282
|
+
{ matcher: "", hooks: [{ type: "command", command: KIMI_HOOK_COMMANDS.UserPromptSubmit }] },
|
|
283
|
+
],
|
|
284
|
+
Stop: [
|
|
285
|
+
{ matcher: "", hooks: [{ type: "command", command: KIMI_HOOK_COMMANDS.Stop }] },
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
readSettings() {
|
|
290
|
+
try {
|
|
291
|
+
const raw = readFileSync(this.getSettingsPath(), "utf-8");
|
|
292
|
+
return { _raw_toml: raw };
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
writeSettings(_settings) {
|
|
299
|
+
// Kimi Code uses TOML format. Writing TOML requires a dedicated
|
|
300
|
+
// serializer. This is a no-op; TOML config should be edited
|
|
301
|
+
// manually or via the context-mode upgrade flow.
|
|
302
|
+
}
|
|
303
|
+
// ── Diagnostics (doctor) ─────────────────────────────────
|
|
304
|
+
validateHooks(_pluginRoot) {
|
|
305
|
+
const results = [];
|
|
306
|
+
const kimiCliVersion = probeKimiCliVersion();
|
|
307
|
+
results.push({
|
|
308
|
+
check: "Kimi Code CLI binary",
|
|
309
|
+
status: kimiCliVersion ? "pass" : "warn",
|
|
310
|
+
message: kimiCliVersion
|
|
311
|
+
? `kimi --version resolved to ${kimiCliVersion}`
|
|
312
|
+
: "Could not run kimi --version; hooks need the Kimi Code CLI available on PATH",
|
|
313
|
+
...(kimiCliVersion ? {} : { fix: "Install Kimi Code CLI or make kimi available on PATH" }),
|
|
314
|
+
});
|
|
315
|
+
// Validate config.toml hooks
|
|
316
|
+
let rawToml = "";
|
|
317
|
+
try {
|
|
318
|
+
rawToml = readFileSync(this.getSettingsPath(), "utf-8");
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
results.push({
|
|
322
|
+
check: "Hooks config",
|
|
323
|
+
status: "fail",
|
|
324
|
+
message: `No readable ${this.getSettingsPath()} found`,
|
|
325
|
+
fix: "Run context-mode upgrade to generate the initial config.toml",
|
|
326
|
+
});
|
|
327
|
+
return results;
|
|
328
|
+
}
|
|
329
|
+
const existingHooks = parseKimiHooks(rawToml);
|
|
330
|
+
const expected = this.generateHookConfig("");
|
|
331
|
+
for (const [hookName, entries] of Object.entries(expected)) {
|
|
332
|
+
const expectedEntry = entries[0];
|
|
333
|
+
const ok = existingHooks.some((h) => h.event === hookName
|
|
334
|
+
&& this.isExpectedHookEntry(hookName, h, expectedEntry));
|
|
335
|
+
const missingStatus = hookName === "PreCompact" ? "warn" : "fail";
|
|
336
|
+
results.push({
|
|
337
|
+
check: `${hookName} hook`,
|
|
338
|
+
status: (ok ? "pass" : missingStatus),
|
|
339
|
+
message: ok
|
|
340
|
+
? `${hookName} hook configured in ${this.getSettingsPath()}`
|
|
341
|
+
: hookName === "PreCompact"
|
|
342
|
+
? `${hookName} hook missing or not pointing to context-mode; compaction snapshots require a Kimi build that emits PreCompact`
|
|
343
|
+
: `${hookName} hook missing or not pointing to context-mode`,
|
|
344
|
+
fix: ok ? undefined : `Update ${this.getSettingsPath()} to include the managed ${hookName} [[hooks]] entry`,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// Surface duplicate context-mode entries per hook event
|
|
348
|
+
for (const hookName of Object.keys(expected)) {
|
|
349
|
+
const managedCount = existingHooks.filter((h) => h.event === hookName && isManagedKimiHook(h)).length;
|
|
350
|
+
if (managedCount > 1) {
|
|
351
|
+
results.push({
|
|
352
|
+
check: `${hookName} duplicates`,
|
|
353
|
+
status: "warn",
|
|
354
|
+
message: `${managedCount} context-mode entries found for ${hookName} in ${this.getSettingsPath()}; Kimi will fire all of them`,
|
|
355
|
+
fix: "context-mode upgrade (collapses duplicate context-mode entries; preserves unrelated hooks)",
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return results;
|
|
360
|
+
}
|
|
361
|
+
checkPluginRegistration() {
|
|
362
|
+
// Check for context-mode in ~/.kimi-code/mcp.json
|
|
363
|
+
try {
|
|
364
|
+
const raw = readFileSync(this.getMcpPath(), "utf-8");
|
|
365
|
+
const parsed = JSON.parse(raw);
|
|
366
|
+
const hasContextMode = raw.includes("context-mode");
|
|
367
|
+
const hasServers = parsed.mcpServers !== undefined || parsed.mcp_servers !== undefined;
|
|
368
|
+
if (hasContextMode && hasServers) {
|
|
369
|
+
return {
|
|
370
|
+
check: "MCP registration",
|
|
371
|
+
status: "pass",
|
|
372
|
+
message: "context-mode found in mcp.json",
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (hasServers) {
|
|
376
|
+
return {
|
|
377
|
+
check: "MCP registration",
|
|
378
|
+
status: "fail",
|
|
379
|
+
message: "mcpServers section exists but context-mode not found",
|
|
380
|
+
fix: `Add context-mode to mcpServers in ${this.getMcpPath()}`,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
check: "MCP registration",
|
|
385
|
+
status: "fail",
|
|
386
|
+
message: "No mcpServers section in mcp.json",
|
|
387
|
+
fix: `Add mcpServers.context-mode to ${this.getMcpPath()}`,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return {
|
|
392
|
+
check: "MCP registration",
|
|
393
|
+
status: "warn",
|
|
394
|
+
message: `Could not read ${this.getMcpPath()}`,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
getInstalledVersion() {
|
|
399
|
+
// Kimi Code uses standalone MCP registration; there is no platform-owned
|
|
400
|
+
// plugin version to compare against the context-mode npm package.
|
|
401
|
+
return "standalone";
|
|
402
|
+
}
|
|
403
|
+
// ── Upgrade ────────────────────────────────────────────
|
|
404
|
+
configureAllHooks(_pluginRoot) {
|
|
405
|
+
const changes = [];
|
|
406
|
+
const desiredHooks = this.generateHookConfig("");
|
|
407
|
+
let rawToml = "";
|
|
408
|
+
try {
|
|
409
|
+
rawToml = readFileSync(this.getSettingsPath(), "utf-8");
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
// File doesn't exist — start fresh
|
|
413
|
+
rawToml = "";
|
|
414
|
+
}
|
|
415
|
+
const existingHooks = parseKimiHooks(rawToml);
|
|
416
|
+
const nonManagedHooks = existingHooks.filter((h) => !isManagedKimiHook(h));
|
|
417
|
+
const managedHooks = [];
|
|
418
|
+
for (const [hookName, entries] of Object.entries(desiredHooks)) {
|
|
419
|
+
const hook = buildKimiHookFromRegistration(hookName, entries[0]);
|
|
420
|
+
if (hook)
|
|
421
|
+
managedHooks.push(hook);
|
|
422
|
+
}
|
|
423
|
+
// Determine if anything changed
|
|
424
|
+
const hadManaged = existingHooks.some(isManagedKimiHook);
|
|
425
|
+
const newToml = this.rebuildToml(rawToml, nonManagedHooks, managedHooks);
|
|
426
|
+
if (newToml !== rawToml) {
|
|
427
|
+
mkdirSync(dirname(this.getSettingsPath()), { recursive: true });
|
|
428
|
+
writeFileSync(this.getSettingsPath(), newToml, "utf-8");
|
|
429
|
+
if (!hadManaged) {
|
|
430
|
+
changes.push(`Wrote managed Kimi hooks to ${this.getSettingsPath()}`);
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
changes.push(`Updated managed Kimi hooks in ${this.getSettingsPath()}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return changes;
|
|
437
|
+
}
|
|
438
|
+
backupSettings() {
|
|
439
|
+
let firstBackupPath = null;
|
|
440
|
+
for (const settingsPath of [this.getSettingsPath(), this.getMcpPath()]) {
|
|
441
|
+
try {
|
|
442
|
+
accessSync(settingsPath, constants.R_OK);
|
|
443
|
+
const backupPath = this.backupFile(settingsPath);
|
|
444
|
+
firstBackupPath ??= backupPath;
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return firstBackupPath;
|
|
451
|
+
}
|
|
452
|
+
setHookPermissions(_pluginRoot) {
|
|
453
|
+
// Hook permissions are set during plugin install
|
|
454
|
+
return [];
|
|
455
|
+
}
|
|
456
|
+
updatePluginRegistry(_pluginRoot, _version) {
|
|
457
|
+
// Kimi Code has no plugin registry
|
|
458
|
+
}
|
|
459
|
+
getRoutingInstructions() {
|
|
460
|
+
const instructionsPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "configs", "kimi", "AGENTS.md");
|
|
461
|
+
try {
|
|
462
|
+
return readFileSync(instructionsPath, "utf-8");
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return "# context-mode\n\nUse context-mode MCP tools (execute, execute_file, batch_execute, fetch_and_index, search) instead of bash/cat/curl for data-heavy operations.";
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// ── Internal helpers ───────────────────────────────────
|
|
469
|
+
getProjectDir(input) {
|
|
470
|
+
return input.cwd ?? process.env.KIMI_PROJECT_DIR ?? process.cwd();
|
|
471
|
+
}
|
|
472
|
+
extractSessionId(input) {
|
|
473
|
+
if (input.session_id)
|
|
474
|
+
return input.session_id;
|
|
475
|
+
return `pid-${process.ppid}`;
|
|
476
|
+
}
|
|
477
|
+
backupFile(filePath, suffix = "") {
|
|
478
|
+
const backupPath = suffix
|
|
479
|
+
? `${filePath}${suffix}-${new Date().toISOString().replace(/[:.]/g, "-")}.bak`
|
|
480
|
+
: `${filePath}.bak`;
|
|
481
|
+
copyFileSync(filePath, backupPath);
|
|
482
|
+
return backupPath;
|
|
483
|
+
}
|
|
484
|
+
isExpectedHookEntry(hookName, hook, expectedEntry) {
|
|
485
|
+
if (hookName === "PreToolUse" && hook.matcher !== expectedEntry.matcher) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
return this.entryContainsManagedCommand(hookName, hook);
|
|
489
|
+
}
|
|
490
|
+
entryContainsManagedCommand(hookName, hook) {
|
|
491
|
+
const normalizedCommand = (hook.command ?? "").replace(/\\/g, "/");
|
|
492
|
+
const expectedCliCommand = (KIMI_HOOK_COMMANDS[hookName] ?? "").replace(/\\/g, "/");
|
|
493
|
+
const legacySuffixes = LEGACY_HOOK_PATH_SUFFIXES[hookName] ?? [];
|
|
494
|
+
return normalizedCommand.includes(expectedCliCommand)
|
|
495
|
+
|| legacySuffixes.some((suffix) => normalizedCommand.includes(suffix));
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Rebuild config.toml by removing old managed hooks and appending new ones.
|
|
499
|
+
* Preserves everything outside [[hooks]] blocks and all non-managed hooks.
|
|
500
|
+
*/
|
|
501
|
+
rebuildToml(rawToml, nonManagedHooks, managedHooks) {
|
|
502
|
+
// Strip all existing [[hooks]] blocks from raw TOML
|
|
503
|
+
const lines = rawToml.split(/\r?\n/);
|
|
504
|
+
const outputLines = [];
|
|
505
|
+
let inHookBlock = false;
|
|
506
|
+
for (const line of lines) {
|
|
507
|
+
if (/^\s*\[\[hooks\]\]\s*(?:#.*)?$/.test(line)) {
|
|
508
|
+
inHookBlock = true;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (inHookBlock) {
|
|
512
|
+
if (/^\s*\[/.test(line)) {
|
|
513
|
+
inHookBlock = false;
|
|
514
|
+
outputLines.push(line);
|
|
515
|
+
}
|
|
516
|
+
// Skip lines belonging to the hook block
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
outputLines.push(line);
|
|
520
|
+
}
|
|
521
|
+
// Trim trailing blank lines (but keep at most one)
|
|
522
|
+
while (outputLines.length > 0 && outputLines[outputLines.length - 1] === "") {
|
|
523
|
+
outputLines.pop();
|
|
524
|
+
}
|
|
525
|
+
if (outputLines.length > 0)
|
|
526
|
+
outputLines.push("");
|
|
527
|
+
// Append non-managed hooks first, then managed ones
|
|
528
|
+
const allHooks = [...nonManagedHooks, ...managedHooks];
|
|
529
|
+
if (allHooks.length > 0) {
|
|
530
|
+
for (const hook of allHooks) {
|
|
531
|
+
outputLines.push(formatKimiHook(hook));
|
|
532
|
+
outputLines.push("");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return outputLines.join("\n");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveKimiConfigDir(): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
export function resolveKimiConfigDir() {
|
|
4
|
+
const envVal = process.env.KIMI_CODE_HOME;
|
|
5
|
+
if (envVal) {
|
|
6
|
+
if (envVal.startsWith("~")) {
|
|
7
|
+
return resolve(homedir(), envVal.replace(/^~[/\\]?/, ""));
|
|
8
|
+
}
|
|
9
|
+
return resolve(envVal);
|
|
10
|
+
}
|
|
11
|
+
return resolve(homedir(), ".kimi-code");
|
|
12
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildHookRuntimeCommand } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* adapters/kiro/hooks — Kiro CLI hook definitions and matchers.
|
|
4
4
|
*
|
|
@@ -88,7 +88,7 @@ export function isContextModeHook(entry, hookType) {
|
|
|
88
88
|
export function buildHookCommand(hookType, pluginRoot) {
|
|
89
89
|
const scriptName = HOOK_SCRIPTS[hookType];
|
|
90
90
|
if (pluginRoot && scriptName) {
|
|
91
|
-
return
|
|
91
|
+
return buildHookRuntimeCommand(`${pluginRoot}/hooks/kiro/${scriptName}`);
|
|
92
92
|
}
|
|
93
93
|
return `context-mode hook kiro ${hookType.toLowerCase()}`;
|
|
94
94
|
}
|
|
@@ -38,9 +38,20 @@ interface CommandContext {
|
|
|
38
38
|
commandBody?: string;
|
|
39
39
|
config?: Record<string, unknown>;
|
|
40
40
|
}
|
|
41
|
+
interface OpenClawCommandDefinition {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
acceptsArgs?: boolean;
|
|
45
|
+
requireAuth?: boolean;
|
|
46
|
+
handler: (ctx: CommandContext) => {
|
|
47
|
+
text: string;
|
|
48
|
+
} | Promise<{
|
|
49
|
+
text: string;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
41
52
|
/** OpenClaw plugin API provided to the register function. */
|
|
42
53
|
interface OpenClawPluginApi {
|
|
43
|
-
registerHook(event: string, handler: (...args: unknown[]) => unknown, meta: {
|
|
54
|
+
registerHook?(event: string, handler: (...args: unknown[]) => unknown, meta: {
|
|
44
55
|
name: string;
|
|
45
56
|
description: string;
|
|
46
57
|
}): void;
|
|
@@ -52,18 +63,8 @@ interface OpenClawPluginApi {
|
|
|
52
63
|
on(event: string, handler: (...args: unknown[]) => unknown, opts?: {
|
|
53
64
|
priority?: number;
|
|
54
65
|
}): void;
|
|
55
|
-
registerContextEngine(id: string, factory: () => ContextEngineInstance): void;
|
|
56
|
-
registerCommand
|
|
57
|
-
name: string;
|
|
58
|
-
description: string;
|
|
59
|
-
acceptsArgs?: boolean;
|
|
60
|
-
requireAuth?: boolean;
|
|
61
|
-
handler: (ctx: CommandContext) => {
|
|
62
|
-
text: string;
|
|
63
|
-
} | Promise<{
|
|
64
|
-
text: string;
|
|
65
|
-
}>;
|
|
66
|
-
}): void;
|
|
66
|
+
registerContextEngine?(id: string, factory: () => ContextEngineInstance): void;
|
|
67
|
+
registerCommand?: (cmd: OpenClawCommandDefinition) => void;
|
|
67
68
|
registerCli?(factory: (ctx: {
|
|
68
69
|
program: unknown;
|
|
69
70
|
}) => void, meta: {
|