context-mode 1.0.89 → 1.0.90
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/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +184 -60
- package/build/adapters/antigravity/index.d.ts +3 -5
- package/build/adapters/antigravity/index.js +7 -35
- package/build/adapters/base.d.ts +27 -0
- package/build/adapters/base.js +59 -0
- package/build/adapters/claude-code/index.d.ts +9 -25
- package/build/adapters/claude-code/index.js +12 -140
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.js +5 -0
- package/build/adapters/codex/hooks.d.ts +21 -14
- package/build/adapters/codex/hooks.js +22 -15
- package/build/adapters/codex/index.d.ts +6 -10
- package/build/adapters/codex/index.js +13 -43
- package/build/adapters/copilot-base.d.ts +78 -0
- package/build/adapters/copilot-base.js +281 -0
- package/build/adapters/cursor/index.d.ts +3 -5
- package/build/adapters/cursor/index.js +6 -34
- package/build/adapters/detect.d.ts +7 -0
- package/build/adapters/detect.js +57 -56
- package/build/adapters/gemini-cli/index.d.ts +3 -5
- package/build/adapters/gemini-cli/index.js +7 -35
- package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
- package/build/adapters/jetbrains-copilot/config.js +8 -0
- package/build/adapters/jetbrains-copilot/hooks.d.ts +51 -0
- package/build/adapters/jetbrains-copilot/hooks.js +82 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +24 -0
- package/build/adapters/jetbrains-copilot/index.js +119 -0
- package/build/adapters/kiro/hooks.d.ts +14 -0
- package/build/adapters/kiro/hooks.js +23 -0
- package/build/adapters/kiro/index.d.ts +3 -5
- package/build/adapters/kiro/index.js +10 -38
- package/build/adapters/openclaw/index.d.ts +3 -4
- package/build/adapters/openclaw/index.js +6 -22
- package/build/adapters/opencode/index.d.ts +2 -3
- package/build/adapters/opencode/index.js +5 -16
- package/build/adapters/qwen-code/index.d.ts +39 -0
- package/build/adapters/qwen-code/index.js +199 -0
- package/build/adapters/types.d.ts +1 -1
- package/build/adapters/vscode-copilot/index.d.ts +16 -46
- package/build/adapters/vscode-copilot/index.js +29 -320
- package/build/adapters/zed/index.d.ts +3 -5
- package/build/adapters/zed/index.js +7 -35
- package/build/cli.js +13 -0
- package/build/lifecycle.d.ts +23 -0
- package/build/lifecycle.js +54 -13
- package/build/opencode-plugin.d.ts +19 -7
- package/build/opencode-plugin.js +19 -7
- package/build/runtime.js +24 -9
- package/build/security.d.ts +17 -1
- package/build/security.js +40 -6
- package/build/server.js +41 -9
- package/build/session/analytics.d.ts +8 -7
- package/build/session/analytics.js +95 -75
- package/build/session/db.d.ts +10 -1
- package/build/session/db.js +67 -8
- package/build/session/extract.js +10 -2
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +231 -0
- package/build/store.d.ts +4 -0
- package/build/store.js +58 -9
- package/build/types.d.ts +8 -0
- package/cli.bundle.mjs +135 -121
- package/configs/antigravity/GEMINI.md +31 -36
- package/configs/claude-code/CLAUDE.md +31 -37
- package/configs/codex/AGENTS.md +35 -49
- package/configs/cursor/context-mode.mdc +24 -25
- package/configs/gemini-cli/GEMINI.md +30 -36
- package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +8 -0
- package/configs/kilo/AGENTS.md +30 -36
- package/configs/kiro/KIRO.md +30 -36
- package/configs/kiro/agent.json +1 -1
- package/configs/openclaw/AGENTS.md +30 -36
- package/configs/opencode/AGENTS.md +30 -36
- package/configs/pi/AGENTS.md +31 -36
- package/configs/qwen-code/QWEN.md +63 -0
- package/configs/vscode-copilot/copilot-instructions.md +30 -36
- package/configs/zed/AGENTS.md +31 -36
- package/hooks/codex/posttooluse.mjs +7 -7
- package/hooks/codex/pretooluse.mjs +3 -3
- package/hooks/codex/sessionstart.mjs +2 -1
- package/hooks/core/formatters.mjs +24 -0
- package/hooks/core/routing.mjs +40 -15
- package/hooks/core/tool-naming.mjs +2 -0
- package/hooks/cursor/posttooluse.mjs +7 -7
- package/hooks/cursor/pretooluse.mjs +3 -3
- package/hooks/cursor/sessionstart.mjs +2 -1
- package/hooks/cursor/stop.mjs +2 -2
- package/hooks/ensure-deps.mjs +22 -10
- package/hooks/gemini-cli/aftertool.mjs +8 -8
- package/hooks/gemini-cli/beforetool.mjs +3 -2
- package/hooks/gemini-cli/precompress.mjs +2 -2
- package/hooks/gemini-cli/sessionstart.mjs +12 -4
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +54 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
- package/hooks/kiro/posttooluse.mjs +6 -7
- package/hooks/kiro/pretooluse.mjs +3 -2
- package/hooks/posttooluse.mjs +8 -8
- package/hooks/precompact.mjs +3 -4
- package/hooks/pretooluse.mjs +5 -4
- package/hooks/routing-block.mjs +35 -33
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +27 -8
- package/hooks/session-extract.bundle.mjs +2 -1
- package/hooks/session-helpers.mjs +44 -3
- package/hooks/session-loaders.mjs +37 -0
- package/hooks/sessionstart.mjs +5 -5
- package/hooks/userpromptsubmit.mjs +26 -9
- package/hooks/vscode-copilot/posttooluse.mjs +8 -8
- package/hooks/vscode-copilot/precompact.mjs +2 -2
- package/hooks/vscode-copilot/pretooluse.mjs +3 -2
- package/hooks/vscode-copilot/sessionstart.mjs +2 -2
- package/insight/server.mjs +237 -25
- package/insight/src/lib/api.ts +2 -1
- package/insight/src/routes/index.tsx +16 -3
- package/insight/src/routes/search.tsx +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +11 -2
- package/server.bundle.mjs +94 -80
- package/skills/ctx-insight/SKILL.md +1 -1
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* adapters/vscode-copilot — VS Code Copilot platform adapter.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* - Blocking: `permissionDecision: "deny"` (same as Claude Code)
|
|
12
|
-
* - Output modification: `additionalContext` in hookSpecificOutput,
|
|
13
|
-
* `decision: "block"` + reason
|
|
14
|
-
* - Tool input fields: tool_name, tool_input (snake_case, same as Claude Code)
|
|
15
|
-
* - But tool input PROPERTY names are camelCase (filePath not file_path)
|
|
16
|
-
* - Session ID: sessionId (camelCase, NOT session_id)
|
|
17
|
-
* - MCP tool prefix: f1e_ (not mcp__server__tool)
|
|
18
|
-
* - CRITICAL: matchers are parsed but IGNORED (all hooks fire on all tools)
|
|
19
|
-
* - Config: .github/hooks/*.json (primary), also reads .claude/settings.json
|
|
20
|
-
* - Env detection: VSCODE_PID, TERM_PROGRAM=vscode
|
|
21
|
-
* - Session dir: ~/.vscode/context-mode/sessions/ (fallback)
|
|
22
|
-
* - Preview status — API may change
|
|
4
|
+
* Extends CopilotBaseAdapter with VS Code-specific logic:
|
|
5
|
+
* - extractSessionId: VSCODE_PID fallback
|
|
6
|
+
* - getProjectDir: CLAUDE_PROJECT_DIR
|
|
7
|
+
* - getSessionDir: .github/ detection with ~/.vscode/ fallback
|
|
8
|
+
* - checkPluginRegistration: reads .vscode/mcp.json
|
|
9
|
+
* - getInstalledVersion: scans VS Code extensions dir
|
|
10
|
+
* - validateHooks: preview status + matcher warnings
|
|
23
11
|
*/
|
|
24
|
-
import {
|
|
25
|
-
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, accessSync, existsSync, chmodSync, constants, } from "node:fs";
|
|
12
|
+
import { readFileSync, mkdirSync, accessSync, existsSync, constants, } from "node:fs";
|
|
26
13
|
import { resolve, join } from "node:path";
|
|
27
14
|
import { homedir } from "node:os";
|
|
15
|
+
import { CopilotBaseAdapter } from "../copilot-base.js";
|
|
28
16
|
// ─────────────────────────────────────────────────────────
|
|
29
17
|
// Hook constants (re-exported from hooks.ts)
|
|
30
18
|
// ─────────────────────────────────────────────────────────
|
|
@@ -32,143 +20,29 @@ import { HOOK_TYPES as VSCODE_HOOK_NAMES, HOOK_SCRIPTS as VSCODE_HOOK_SCRIPTS, b
|
|
|
32
20
|
// ─────────────────────────────────────────────────────────
|
|
33
21
|
// Adapter implementation
|
|
34
22
|
// ─────────────────────────────────────────────────────────
|
|
35
|
-
export class VSCodeCopilotAdapter {
|
|
23
|
+
export class VSCodeCopilotAdapter extends CopilotBaseAdapter {
|
|
24
|
+
constructor() {
|
|
25
|
+
// sessionDirSegments unused — vscode-copilot overrides getSessionDir()
|
|
26
|
+
// with .github directory detection fallback logic
|
|
27
|
+
super([".vscode"]);
|
|
28
|
+
}
|
|
36
29
|
name = "VS Code Copilot";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
preCompact: true,
|
|
42
|
-
sessionStart: true,
|
|
43
|
-
canModifyArgs: true,
|
|
44
|
-
canModifyOutput: true,
|
|
45
|
-
canInjectSessionContext: true,
|
|
30
|
+
hookModule = {
|
|
31
|
+
HOOK_TYPES: VSCODE_HOOK_NAMES,
|
|
32
|
+
HOOK_SCRIPTS: VSCODE_HOOK_SCRIPTS,
|
|
33
|
+
buildHookCommand: buildVSCodeHookCommand,
|
|
46
34
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
raw,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
parsePostToolUseInput(raw) {
|
|
59
|
-
const input = raw;
|
|
60
|
-
return {
|
|
61
|
-
toolName: input.tool_name ?? "",
|
|
62
|
-
toolInput: input.tool_input ?? {},
|
|
63
|
-
toolOutput: input.tool_output,
|
|
64
|
-
isError: input.is_error,
|
|
65
|
-
sessionId: this.extractSessionId(input),
|
|
66
|
-
projectDir: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
67
|
-
raw,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
parsePreCompactInput(raw) {
|
|
71
|
-
const input = raw;
|
|
72
|
-
return {
|
|
73
|
-
sessionId: this.extractSessionId(input),
|
|
74
|
-
projectDir: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
75
|
-
raw,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
parseSessionStartInput(raw) {
|
|
79
|
-
const input = raw;
|
|
80
|
-
const rawSource = input.source ?? "startup";
|
|
81
|
-
let source;
|
|
82
|
-
switch (rawSource) {
|
|
83
|
-
case "compact":
|
|
84
|
-
source = "compact";
|
|
85
|
-
break;
|
|
86
|
-
case "resume":
|
|
87
|
-
source = "resume";
|
|
88
|
-
break;
|
|
89
|
-
case "clear":
|
|
90
|
-
source = "clear";
|
|
91
|
-
break;
|
|
92
|
-
default:
|
|
93
|
-
source = "startup";
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
sessionId: this.extractSessionId(input),
|
|
97
|
-
source,
|
|
98
|
-
projectDir: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
99
|
-
raw,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
// ── Response formatting ────────────────────────────────
|
|
103
|
-
formatPreToolUseResponse(response) {
|
|
104
|
-
if (response.decision === "deny") {
|
|
105
|
-
return {
|
|
106
|
-
permissionDecision: "deny",
|
|
107
|
-
reason: response.reason ?? "Blocked by context-mode hook",
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (response.decision === "modify" && response.updatedInput) {
|
|
111
|
-
// VS Code Copilot: updatedInput is wrapped in hookSpecificOutput
|
|
112
|
-
return {
|
|
113
|
-
hookSpecificOutput: {
|
|
114
|
-
hookEventName: VSCODE_HOOK_NAMES.PRE_TOOL_USE,
|
|
115
|
-
updatedInput: response.updatedInput,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
if (response.decision === "context" && response.additionalContext) {
|
|
120
|
-
// VS Code Copilot: inject additionalContext via hookSpecificOutput
|
|
121
|
-
return {
|
|
122
|
-
hookSpecificOutput: {
|
|
123
|
-
hookEventName: VSCODE_HOOK_NAMES.PRE_TOOL_USE,
|
|
124
|
-
additionalContext: response.additionalContext,
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
if (response.decision === "ask") {
|
|
129
|
-
// VS Code Copilot: use deny to force user attention (no native "ask")
|
|
130
|
-
return {
|
|
131
|
-
permissionDecision: "deny",
|
|
132
|
-
reason: response.reason ?? "Action requires user confirmation (security policy)",
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
// "allow" — return undefined for passthrough
|
|
136
|
-
return undefined;
|
|
137
|
-
}
|
|
138
|
-
formatPostToolUseResponse(response) {
|
|
139
|
-
if (response.updatedOutput) {
|
|
140
|
-
// VS Code Copilot: decision "block" + reason for output replacement
|
|
141
|
-
return {
|
|
142
|
-
hookSpecificOutput: {
|
|
143
|
-
hookEventName: VSCODE_HOOK_NAMES.POST_TOOL_USE,
|
|
144
|
-
decision: "block",
|
|
145
|
-
reason: response.updatedOutput,
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
if (response.additionalContext) {
|
|
150
|
-
return {
|
|
151
|
-
hookSpecificOutput: {
|
|
152
|
-
hookEventName: VSCODE_HOOK_NAMES.POST_TOOL_USE,
|
|
153
|
-
additionalContext: response.additionalContext,
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
return undefined;
|
|
158
|
-
}
|
|
159
|
-
formatPreCompactResponse(response) {
|
|
160
|
-
// VS Code Copilot: stdout content on exit 0 is injected as context
|
|
161
|
-
return response.context ?? "";
|
|
162
|
-
}
|
|
163
|
-
formatSessionStartResponse(response) {
|
|
164
|
-
// VS Code Copilot: stdout content is injected as additional context
|
|
165
|
-
return response.context ?? "";
|
|
35
|
+
hookSubdir = "vscode-copilot";
|
|
36
|
+
// ── Platform-specific overrides ────────────────────────
|
|
37
|
+
extractSessionId(input) {
|
|
38
|
+
if (input.sessionId)
|
|
39
|
+
return input.sessionId;
|
|
40
|
+
if (process.env.VSCODE_PID)
|
|
41
|
+
return `vscode-${process.env.VSCODE_PID}`;
|
|
42
|
+
return `pid-${process.ppid}`;
|
|
166
43
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// VS Code Copilot primarily uses .github/hooks/*.json
|
|
170
|
-
// but also reads .claude/settings.json
|
|
171
|
-
return resolve(".github", "hooks", "context-mode.json");
|
|
44
|
+
getProjectDir() {
|
|
45
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
172
46
|
}
|
|
173
47
|
getSessionDir() {
|
|
174
48
|
// Prefer .github/context-mode/sessions/ if .github exists,
|
|
@@ -179,91 +53,6 @@ export class VSCodeCopilotAdapter {
|
|
|
179
53
|
mkdirSync(dir, { recursive: true });
|
|
180
54
|
return dir;
|
|
181
55
|
}
|
|
182
|
-
getSessionDBPath(projectDir) {
|
|
183
|
-
const hash = createHash("sha256")
|
|
184
|
-
.update(projectDir)
|
|
185
|
-
.digest("hex")
|
|
186
|
-
.slice(0, 16);
|
|
187
|
-
return join(this.getSessionDir(), `${hash}.db`);
|
|
188
|
-
}
|
|
189
|
-
getSessionEventsPath(projectDir) {
|
|
190
|
-
const hash = createHash("sha256")
|
|
191
|
-
.update(projectDir)
|
|
192
|
-
.digest("hex")
|
|
193
|
-
.slice(0, 16);
|
|
194
|
-
return join(this.getSessionDir(), `${hash}-events.md`);
|
|
195
|
-
}
|
|
196
|
-
generateHookConfig(pluginRoot) {
|
|
197
|
-
return {
|
|
198
|
-
[VSCODE_HOOK_NAMES.PRE_TOOL_USE]: [
|
|
199
|
-
{
|
|
200
|
-
matcher: "",
|
|
201
|
-
hooks: [
|
|
202
|
-
{
|
|
203
|
-
type: "command",
|
|
204
|
-
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.PRE_TOOL_USE, pluginRoot),
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
},
|
|
208
|
-
],
|
|
209
|
-
[VSCODE_HOOK_NAMES.POST_TOOL_USE]: [
|
|
210
|
-
{
|
|
211
|
-
matcher: "",
|
|
212
|
-
hooks: [
|
|
213
|
-
{
|
|
214
|
-
type: "command",
|
|
215
|
-
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.POST_TOOL_USE, pluginRoot),
|
|
216
|
-
},
|
|
217
|
-
],
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
[VSCODE_HOOK_NAMES.PRE_COMPACT]: [
|
|
221
|
-
{
|
|
222
|
-
matcher: "",
|
|
223
|
-
hooks: [
|
|
224
|
-
{
|
|
225
|
-
type: "command",
|
|
226
|
-
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.PRE_COMPACT, pluginRoot),
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
[VSCODE_HOOK_NAMES.SESSION_START]: [
|
|
232
|
-
{
|
|
233
|
-
matcher: "",
|
|
234
|
-
hooks: [
|
|
235
|
-
{
|
|
236
|
-
type: "command",
|
|
237
|
-
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.SESSION_START, pluginRoot),
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
readSettings() {
|
|
245
|
-
// Try .github/hooks/context-mode.json first, then .claude/settings.json
|
|
246
|
-
const paths = [
|
|
247
|
-
this.getSettingsPath(),
|
|
248
|
-
resolve(".claude", "settings.json"),
|
|
249
|
-
];
|
|
250
|
-
for (const configPath of paths) {
|
|
251
|
-
try {
|
|
252
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
253
|
-
return JSON.parse(raw);
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
writeSettings(settings) {
|
|
262
|
-
const configPath = this.getSettingsPath();
|
|
263
|
-
const dir = resolve(".github", "hooks");
|
|
264
|
-
mkdirSync(dir, { recursive: true });
|
|
265
|
-
writeFileSync(configPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
266
|
-
}
|
|
267
56
|
// ── Diagnostics (doctor) ─────────────────────────────────
|
|
268
57
|
validateHooks(pluginRoot) {
|
|
269
58
|
const results = [];
|
|
@@ -397,84 +186,4 @@ export class VSCodeCopilotAdapter {
|
|
|
397
186
|
}
|
|
398
187
|
return "not installed";
|
|
399
188
|
}
|
|
400
|
-
// ── Upgrade ────────────────────────────────────────────
|
|
401
|
-
configureAllHooks(pluginRoot) {
|
|
402
|
-
const changes = [];
|
|
403
|
-
const hookConfig = { hooks: {} };
|
|
404
|
-
const hooks = hookConfig.hooks;
|
|
405
|
-
const hookTypes = [
|
|
406
|
-
VSCODE_HOOK_NAMES.PRE_TOOL_USE,
|
|
407
|
-
VSCODE_HOOK_NAMES.POST_TOOL_USE,
|
|
408
|
-
VSCODE_HOOK_NAMES.PRE_COMPACT,
|
|
409
|
-
VSCODE_HOOK_NAMES.SESSION_START,
|
|
410
|
-
];
|
|
411
|
-
for (const hookType of hookTypes) {
|
|
412
|
-
const script = VSCODE_HOOK_SCRIPTS[hookType];
|
|
413
|
-
if (!script)
|
|
414
|
-
continue;
|
|
415
|
-
hooks[hookType] = [
|
|
416
|
-
{
|
|
417
|
-
matcher: "",
|
|
418
|
-
hooks: [
|
|
419
|
-
{
|
|
420
|
-
type: "command",
|
|
421
|
-
command: buildVSCodeHookCommand(hookType, pluginRoot),
|
|
422
|
-
},
|
|
423
|
-
],
|
|
424
|
-
},
|
|
425
|
-
];
|
|
426
|
-
changes.push(`Configured ${hookType} hook`);
|
|
427
|
-
}
|
|
428
|
-
// Write to .github/hooks/context-mode.json
|
|
429
|
-
const outputDir = resolve(".github", "hooks");
|
|
430
|
-
mkdirSync(outputDir, { recursive: true });
|
|
431
|
-
const outputPath = resolve(outputDir, "context-mode.json");
|
|
432
|
-
writeFileSync(outputPath, JSON.stringify(hookConfig, null, 2) + "\n", "utf-8");
|
|
433
|
-
changes.push(`Wrote hook config to ${outputPath}`);
|
|
434
|
-
return changes;
|
|
435
|
-
}
|
|
436
|
-
backupSettings() {
|
|
437
|
-
const settingsPath = this.getSettingsPath();
|
|
438
|
-
try {
|
|
439
|
-
accessSync(settingsPath, constants.R_OK);
|
|
440
|
-
const backupPath = settingsPath + ".bak";
|
|
441
|
-
copyFileSync(settingsPath, backupPath);
|
|
442
|
-
return backupPath;
|
|
443
|
-
}
|
|
444
|
-
catch {
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
setHookPermissions(pluginRoot) {
|
|
449
|
-
const set = [];
|
|
450
|
-
const hooksDir = join(pluginRoot, "hooks", "vscode-copilot");
|
|
451
|
-
for (const scriptName of Object.values(VSCODE_HOOK_SCRIPTS)) {
|
|
452
|
-
const scriptPath = resolve(hooksDir, scriptName);
|
|
453
|
-
try {
|
|
454
|
-
accessSync(scriptPath, constants.R_OK);
|
|
455
|
-
chmodSync(scriptPath, 0o755);
|
|
456
|
-
set.push(scriptPath);
|
|
457
|
-
}
|
|
458
|
-
catch {
|
|
459
|
-
/* skip missing scripts */
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return set;
|
|
463
|
-
}
|
|
464
|
-
updatePluginRegistry(_pluginRoot, _version) {
|
|
465
|
-
// VS Code manages extensions through its own marketplace/extension system.
|
|
466
|
-
// No manual registry update needed.
|
|
467
|
-
}
|
|
468
|
-
// ── Internal helpers ───────────────────────────────────
|
|
469
|
-
/**
|
|
470
|
-
* Extract session ID from VS Code Copilot hook input.
|
|
471
|
-
* VS Code Copilot uses camelCase sessionId (NOT session_id).
|
|
472
|
-
*/
|
|
473
|
-
extractSessionId(input) {
|
|
474
|
-
if (input.sessionId)
|
|
475
|
-
return input.sessionId;
|
|
476
|
-
if (process.env.VSCODE_PID)
|
|
477
|
-
return `vscode-${process.env.VSCODE_PID}`;
|
|
478
|
-
return `pid-${process.ppid}`;
|
|
479
|
-
}
|
|
480
189
|
}
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* - All capabilities are false — MCP is the only integration path
|
|
11
11
|
* - Session dir: ~/.config/zed/context-mode/sessions/
|
|
12
12
|
*/
|
|
13
|
+
import { BaseAdapter } from "../base.js";
|
|
13
14
|
import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration } from "../types.js";
|
|
14
|
-
export declare class ZedAdapter implements HookAdapter {
|
|
15
|
+
export declare class ZedAdapter extends BaseAdapter implements HookAdapter {
|
|
16
|
+
constructor();
|
|
15
17
|
readonly name = "Zed";
|
|
16
18
|
readonly paradigm: HookParadigm;
|
|
17
19
|
readonly capabilities: PlatformCapabilities;
|
|
@@ -24,9 +26,6 @@ export declare class ZedAdapter implements HookAdapter {
|
|
|
24
26
|
formatPreCompactResponse(_response: PreCompactResponse): unknown;
|
|
25
27
|
formatSessionStartResponse(_response: SessionStartResponse): unknown;
|
|
26
28
|
getSettingsPath(): string;
|
|
27
|
-
getSessionDir(): string;
|
|
28
|
-
getSessionDBPath(projectDir: string): string;
|
|
29
|
-
getSessionEventsPath(projectDir: string): string;
|
|
30
29
|
generateHookConfig(_pluginRoot: string): HookRegistration;
|
|
31
30
|
readSettings(): Record<string, unknown> | null;
|
|
32
31
|
writeSettings(settings: Record<string, unknown>): void;
|
|
@@ -34,7 +33,6 @@ export declare class ZedAdapter implements HookAdapter {
|
|
|
34
33
|
checkPluginRegistration(): DiagnosticResult;
|
|
35
34
|
getInstalledVersion(): string;
|
|
36
35
|
configureAllHooks(_pluginRoot: string): string[];
|
|
37
|
-
backupSettings(): string | null;
|
|
38
36
|
setHookPermissions(_pluginRoot: string): string[];
|
|
39
37
|
updatePluginRegistry(_pluginRoot: string, _version: string): void;
|
|
40
38
|
getRoutingInstructions(): string;
|
|
@@ -10,15 +10,18 @@
|
|
|
10
10
|
* - All capabilities are false — MCP is the only integration path
|
|
11
11
|
* - Session dir: ~/.config/zed/context-mode/sessions/
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { resolve, join, dirname } from "node:path";
|
|
13
|
+
import { readFileSync, writeFileSync, mkdirSync, } from "node:fs";
|
|
14
|
+
import { resolve, dirname } from "node:path";
|
|
16
15
|
import { fileURLToPath } from "node:url";
|
|
17
16
|
import { homedir } from "node:os";
|
|
17
|
+
import { BaseAdapter } from "../base.js";
|
|
18
18
|
// ─────────────────────────────────────────────────────────
|
|
19
19
|
// Adapter implementation
|
|
20
20
|
// ─────────────────────────────────────────────────────────
|
|
21
|
-
export class ZedAdapter {
|
|
21
|
+
export class ZedAdapter extends BaseAdapter {
|
|
22
|
+
constructor() {
|
|
23
|
+
super([".config", "zed"]);
|
|
24
|
+
}
|
|
22
25
|
name = "Zed";
|
|
23
26
|
paradigm = "mcp-only";
|
|
24
27
|
capabilities = {
|
|
@@ -63,25 +66,6 @@ export class ZedAdapter {
|
|
|
63
66
|
getSettingsPath() {
|
|
64
67
|
return resolve(homedir(), ".config", "zed", "settings.json");
|
|
65
68
|
}
|
|
66
|
-
getSessionDir() {
|
|
67
|
-
const dir = join(homedir(), ".config", "zed", "context-mode", "sessions");
|
|
68
|
-
mkdirSync(dir, { recursive: true });
|
|
69
|
-
return dir;
|
|
70
|
-
}
|
|
71
|
-
getSessionDBPath(projectDir) {
|
|
72
|
-
const hash = createHash("sha256")
|
|
73
|
-
.update(projectDir)
|
|
74
|
-
.digest("hex")
|
|
75
|
-
.slice(0, 16);
|
|
76
|
-
return join(this.getSessionDir(), `${hash}.db`);
|
|
77
|
-
}
|
|
78
|
-
getSessionEventsPath(projectDir) {
|
|
79
|
-
const hash = createHash("sha256")
|
|
80
|
-
.update(projectDir)
|
|
81
|
-
.digest("hex")
|
|
82
|
-
.slice(0, 16);
|
|
83
|
-
return join(this.getSessionDir(), `${hash}-events.md`);
|
|
84
|
-
}
|
|
85
69
|
generateHookConfig(_pluginRoot) {
|
|
86
70
|
// Zed does not support hooks — return empty registration
|
|
87
71
|
return {};
|
|
@@ -156,18 +140,6 @@ export class ZedAdapter {
|
|
|
156
140
|
// Zed does not support hooks — nothing to configure
|
|
157
141
|
return [];
|
|
158
142
|
}
|
|
159
|
-
backupSettings() {
|
|
160
|
-
const settingsPath = this.getSettingsPath();
|
|
161
|
-
try {
|
|
162
|
-
accessSync(settingsPath, constants.R_OK);
|
|
163
|
-
const backupPath = settingsPath + ".bak";
|
|
164
|
-
copyFileSync(settingsPath, backupPath);
|
|
165
|
-
return backupPath;
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
143
|
setHookPermissions(_pluginRoot) {
|
|
172
144
|
// No hook scripts for Zed
|
|
173
145
|
return [];
|
package/build/cli.js
CHANGED
|
@@ -60,6 +60,19 @@ const HOOK_MAP = {
|
|
|
60
60
|
pretooluse: "hooks/kiro/pretooluse.mjs",
|
|
61
61
|
posttooluse: "hooks/kiro/posttooluse.mjs",
|
|
62
62
|
},
|
|
63
|
+
"jetbrains-copilot": {
|
|
64
|
+
pretooluse: "hooks/jetbrains-copilot/pretooluse.mjs",
|
|
65
|
+
posttooluse: "hooks/jetbrains-copilot/posttooluse.mjs",
|
|
66
|
+
precompact: "hooks/jetbrains-copilot/precompact.mjs",
|
|
67
|
+
sessionstart: "hooks/jetbrains-copilot/sessionstart.mjs",
|
|
68
|
+
},
|
|
69
|
+
"qwen-code": {
|
|
70
|
+
pretooluse: "hooks/pretooluse.mjs",
|
|
71
|
+
posttooluse: "hooks/posttooluse.mjs",
|
|
72
|
+
precompact: "hooks/precompact.mjs",
|
|
73
|
+
sessionstart: "hooks/sessionstart.mjs",
|
|
74
|
+
userpromptsubmit: "hooks/userpromptsubmit.mjs",
|
|
75
|
+
},
|
|
63
76
|
};
|
|
64
77
|
async function hookDispatch(platform, event) {
|
|
65
78
|
// Suppress stderr at OS fd level — native C++ modules (better-sqlite3) write
|
package/build/lifecycle.d.ts
CHANGED
|
@@ -17,6 +17,29 @@ export interface LifecycleGuardOptions {
|
|
|
17
17
|
/** Injectable parent-alive check (for testing). Default: ppid-based check. */
|
|
18
18
|
isParentAlive?: () => boolean;
|
|
19
19
|
}
|
|
20
|
+
/** Injectable dependencies for {@link makeDefaultIsParentAlive}. */
|
|
21
|
+
export interface IsParentAliveDeps {
|
|
22
|
+
/** Read the current ppid. Default: `() => process.ppid`. */
|
|
23
|
+
getPpid?: () => number;
|
|
24
|
+
/** Read the grandparent ppid. Default: ps-based POSIX probe, NaN on Windows. */
|
|
25
|
+
readGrandparentPpid?: () => number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build a parent-liveness check that handles the npm-exec wrapper case (#311).
|
|
29
|
+
*
|
|
30
|
+
* A plain ppid comparison misses Claude Code sessions launched via
|
|
31
|
+
* `start.mjs → npm exec → context-mode server`: when Claude Code dies,
|
|
32
|
+
* `start.mjs` reparents to init but `npm exec` stays alive, so the server's
|
|
33
|
+
* direct ppid never changes. We additionally check whether the grandparent
|
|
34
|
+
* process has been reparented to init (PID 1). When the original grandparent
|
|
35
|
+
* was already 1 (daemonized startup) the check is skipped, and on Windows
|
|
36
|
+
* where there's no cheap `ps` equivalent we also skip — so this change is
|
|
37
|
+
* strictly additive to the previous behavior.
|
|
38
|
+
*
|
|
39
|
+
* Exported for unit-testing with injected readers. Production code uses
|
|
40
|
+
* {@link defaultIsParentAlive} (captured once at module load).
|
|
41
|
+
*/
|
|
42
|
+
export declare function makeDefaultIsParentAlive(deps?: IsParentAliveDeps): () => boolean;
|
|
20
43
|
/**
|
|
21
44
|
* Start the lifecycle guard. Returns a cleanup function.
|
|
22
45
|
* Skipped automatically when stdin is a TTY (e.g. OpenCode ts-plugin).
|
package/build/lifecycle.js
CHANGED
|
@@ -9,23 +9,64 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Cross-platform: macOS, Linux, Windows.
|
|
11
11
|
*/
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
/** Read grandparent PID via `ps -o ppid= -p $PPID`. Returns NaN on failure or Windows. */
|
|
14
|
+
function readGrandparentPpidImpl() {
|
|
15
|
+
if (process.platform === "win32")
|
|
16
|
+
return NaN;
|
|
17
|
+
const ppid = process.ppid;
|
|
18
|
+
if (!ppid || ppid <= 1)
|
|
19
|
+
return NaN;
|
|
20
|
+
try {
|
|
21
|
+
const out = execFileSync("ps", ["-o", "ppid=", "-p", String(ppid)], {
|
|
22
|
+
encoding: "utf-8",
|
|
23
|
+
timeout: 2000,
|
|
24
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
25
|
+
}).trim();
|
|
26
|
+
const n = parseInt(out, 10);
|
|
27
|
+
return Number.isFinite(n) ? n : NaN;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return NaN;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
12
33
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
34
|
+
* Build a parent-liveness check that handles the npm-exec wrapper case (#311).
|
|
35
|
+
*
|
|
36
|
+
* A plain ppid comparison misses Claude Code sessions launched via
|
|
37
|
+
* `start.mjs → npm exec → context-mode server`: when Claude Code dies,
|
|
38
|
+
* `start.mjs` reparents to init but `npm exec` stays alive, so the server's
|
|
39
|
+
* direct ppid never changes. We additionally check whether the grandparent
|
|
40
|
+
* process has been reparented to init (PID 1). When the original grandparent
|
|
41
|
+
* was already 1 (daemonized startup) the check is skipped, and on Windows
|
|
42
|
+
* where there's no cheap `ps` equivalent we also skip — so this change is
|
|
43
|
+
* strictly additive to the previous behavior.
|
|
17
44
|
*
|
|
18
|
-
*
|
|
45
|
+
* Exported for unit-testing with injected readers. Production code uses
|
|
46
|
+
* {@link defaultIsParentAlive} (captured once at module load).
|
|
19
47
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
export function makeDefaultIsParentAlive(deps = {}) {
|
|
49
|
+
const getPpid = deps.getPpid ?? (() => process.ppid);
|
|
50
|
+
const readGp = deps.readGrandparentPpid ?? readGrandparentPpidImpl;
|
|
51
|
+
const originalPpid = getPpid();
|
|
52
|
+
const originalGrandparentPpid = readGp();
|
|
53
|
+
return () => {
|
|
54
|
+
const ppid = getPpid();
|
|
55
|
+
if (ppid !== originalPpid)
|
|
56
|
+
return false;
|
|
57
|
+
if (ppid === 0 || ppid === 1)
|
|
58
|
+
return false;
|
|
59
|
+
// Grandparent orphan check (#311): npm-exec wrappers stay alive past the
|
|
60
|
+
// session owner. If our grandparent is now PID 1 but wasn't at startup,
|
|
61
|
+
// the wrapping chain is orphaned and we should shut down.
|
|
62
|
+
if (!Number.isNaN(originalGrandparentPpid) && originalGrandparentPpid > 1) {
|
|
63
|
+
if (readGp() === 1)
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
28
68
|
}
|
|
69
|
+
const defaultIsParentAlive = makeDefaultIsParentAlive();
|
|
29
70
|
/**
|
|
30
71
|
* Start the lifecycle guard. Returns a cleanup function.
|
|
31
72
|
* Skipped automatically when stdin is a TTY (e.g. OpenCode ts-plugin).
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenCode TypeScript plugin entry point for context-mode.
|
|
2
|
+
* OpenCode / KiloCode TypeScript plugin entry point for context-mode.
|
|
3
3
|
*
|
|
4
4
|
* Provides three hooks:
|
|
5
5
|
* - tool.execute.before — Routing enforcement (deny/modify/passthrough)
|
|
6
6
|
* - tool.execute.after — Session event capture
|
|
7
7
|
* - experimental.session.compacting — Compaction snapshot generation
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* KiloCode loads this via: import("context-mode") → expects default export
|
|
10
|
+
* with shape { server: (input) => Promise<Hooks> } (PluginModule).
|
|
11
|
+
*
|
|
12
|
+
* OpenCode loads this via: import("context-mode/plugin") → also supports
|
|
13
|
+
* the named export ContextModePlugin for backward compat.
|
|
10
14
|
*
|
|
11
15
|
* Constraints:
|
|
12
16
|
* - No SessionStart hook (OpenCode doesn't support it — #14808, #5409)
|
|
@@ -14,9 +18,10 @@
|
|
|
14
18
|
* - No routing file auto-write (avoid dirtying project trees)
|
|
15
19
|
* - Session cleanup happens at plugin init (no SessionStart)
|
|
16
20
|
*/
|
|
17
|
-
/** OpenCode plugin
|
|
21
|
+
/** KiloCode/OpenCode plugin input — both platforms pass at least `directory`. */
|
|
18
22
|
interface PluginContext {
|
|
19
23
|
directory: string;
|
|
24
|
+
[key: string]: unknown;
|
|
20
25
|
}
|
|
21
26
|
/** OpenCode tool.execute.before — first parameter */
|
|
22
27
|
interface BeforeHookInput {
|
|
@@ -51,12 +56,19 @@ interface CompactingHookOutput {
|
|
|
51
56
|
prompt?: string;
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
54
|
-
*
|
|
59
|
+
* Plugin factory. Called once when KiloCode/OpenCode loads the plugin.
|
|
55
60
|
* Returns an object mapping hook event names to async handler functions.
|
|
56
|
-
|
|
57
|
-
export
|
|
61
|
+
*
|
|
62
|
+
* KiloCode expects: export default { server: (input) => Promise<Hooks> }
|
|
63
|
+
* OpenCode expects: export const ContextModePlugin = (ctx) => Promise<Hooks>
|
|
64
|
+
*/
|
|
65
|
+
declare function createContextModePlugin(ctx: PluginContext): Promise<{
|
|
58
66
|
"tool.execute.before": (input: BeforeHookInput, output: BeforeHookOutput) => Promise<void>;
|
|
59
67
|
"tool.execute.after": (input: AfterHookInput, output: AfterHookOutput) => Promise<void>;
|
|
60
68
|
"experimental.session.compacting": (input: CompactingHookInput, output: CompactingHookOutput) => Promise<string>;
|
|
61
69
|
}>;
|
|
62
|
-
|
|
70
|
+
declare const _default: {
|
|
71
|
+
server: typeof createContextModePlugin;
|
|
72
|
+
};
|
|
73
|
+
export default _default;
|
|
74
|
+
export { createContextModePlugin as ContextModePlugin };
|