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.
Files changed (128) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  4. package/.openclaw-plugin/package.json +1 -1
  5. package/README.md +184 -60
  6. package/build/adapters/antigravity/index.d.ts +3 -5
  7. package/build/adapters/antigravity/index.js +7 -35
  8. package/build/adapters/base.d.ts +27 -0
  9. package/build/adapters/base.js +59 -0
  10. package/build/adapters/claude-code/index.d.ts +9 -25
  11. package/build/adapters/claude-code/index.js +12 -140
  12. package/build/adapters/claude-code-base.d.ts +49 -0
  13. package/build/adapters/claude-code-base.js +113 -0
  14. package/build/adapters/client-map.js +5 -0
  15. package/build/adapters/codex/hooks.d.ts +21 -14
  16. package/build/adapters/codex/hooks.js +22 -15
  17. package/build/adapters/codex/index.d.ts +6 -10
  18. package/build/adapters/codex/index.js +13 -43
  19. package/build/adapters/copilot-base.d.ts +78 -0
  20. package/build/adapters/copilot-base.js +281 -0
  21. package/build/adapters/cursor/index.d.ts +3 -5
  22. package/build/adapters/cursor/index.js +6 -34
  23. package/build/adapters/detect.d.ts +7 -0
  24. package/build/adapters/detect.js +57 -56
  25. package/build/adapters/gemini-cli/index.d.ts +3 -5
  26. package/build/adapters/gemini-cli/index.js +7 -35
  27. package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
  28. package/build/adapters/jetbrains-copilot/config.js +8 -0
  29. package/build/adapters/jetbrains-copilot/hooks.d.ts +51 -0
  30. package/build/adapters/jetbrains-copilot/hooks.js +82 -0
  31. package/build/adapters/jetbrains-copilot/index.d.ts +24 -0
  32. package/build/adapters/jetbrains-copilot/index.js +119 -0
  33. package/build/adapters/kiro/hooks.d.ts +14 -0
  34. package/build/adapters/kiro/hooks.js +23 -0
  35. package/build/adapters/kiro/index.d.ts +3 -5
  36. package/build/adapters/kiro/index.js +10 -38
  37. package/build/adapters/openclaw/index.d.ts +3 -4
  38. package/build/adapters/openclaw/index.js +6 -22
  39. package/build/adapters/opencode/index.d.ts +2 -3
  40. package/build/adapters/opencode/index.js +5 -16
  41. package/build/adapters/qwen-code/index.d.ts +39 -0
  42. package/build/adapters/qwen-code/index.js +199 -0
  43. package/build/adapters/types.d.ts +1 -1
  44. package/build/adapters/vscode-copilot/index.d.ts +16 -46
  45. package/build/adapters/vscode-copilot/index.js +29 -320
  46. package/build/adapters/zed/index.d.ts +3 -5
  47. package/build/adapters/zed/index.js +7 -35
  48. package/build/cli.js +13 -0
  49. package/build/lifecycle.d.ts +23 -0
  50. package/build/lifecycle.js +54 -13
  51. package/build/opencode-plugin.d.ts +19 -7
  52. package/build/opencode-plugin.js +19 -7
  53. package/build/runtime.js +24 -9
  54. package/build/security.d.ts +17 -1
  55. package/build/security.js +40 -6
  56. package/build/server.js +41 -9
  57. package/build/session/analytics.d.ts +8 -7
  58. package/build/session/analytics.js +95 -75
  59. package/build/session/db.d.ts +10 -1
  60. package/build/session/db.js +67 -8
  61. package/build/session/extract.js +10 -2
  62. package/build/session/project-attribution.d.ts +73 -0
  63. package/build/session/project-attribution.js +231 -0
  64. package/build/store.d.ts +4 -0
  65. package/build/store.js +58 -9
  66. package/build/types.d.ts +8 -0
  67. package/cli.bundle.mjs +135 -121
  68. package/configs/antigravity/GEMINI.md +31 -36
  69. package/configs/claude-code/CLAUDE.md +31 -37
  70. package/configs/codex/AGENTS.md +35 -49
  71. package/configs/cursor/context-mode.mdc +24 -25
  72. package/configs/gemini-cli/GEMINI.md +30 -36
  73. package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
  74. package/configs/jetbrains-copilot/hooks.json +16 -0
  75. package/configs/jetbrains-copilot/mcp.json +8 -0
  76. package/configs/kilo/AGENTS.md +30 -36
  77. package/configs/kiro/KIRO.md +30 -36
  78. package/configs/kiro/agent.json +1 -1
  79. package/configs/openclaw/AGENTS.md +30 -36
  80. package/configs/opencode/AGENTS.md +30 -36
  81. package/configs/pi/AGENTS.md +31 -36
  82. package/configs/qwen-code/QWEN.md +63 -0
  83. package/configs/vscode-copilot/copilot-instructions.md +30 -36
  84. package/configs/zed/AGENTS.md +31 -36
  85. package/hooks/codex/posttooluse.mjs +7 -7
  86. package/hooks/codex/pretooluse.mjs +3 -3
  87. package/hooks/codex/sessionstart.mjs +2 -1
  88. package/hooks/core/formatters.mjs +24 -0
  89. package/hooks/core/routing.mjs +40 -15
  90. package/hooks/core/tool-naming.mjs +2 -0
  91. package/hooks/cursor/posttooluse.mjs +7 -7
  92. package/hooks/cursor/pretooluse.mjs +3 -3
  93. package/hooks/cursor/sessionstart.mjs +2 -1
  94. package/hooks/cursor/stop.mjs +2 -2
  95. package/hooks/ensure-deps.mjs +22 -10
  96. package/hooks/gemini-cli/aftertool.mjs +8 -8
  97. package/hooks/gemini-cli/beforetool.mjs +3 -2
  98. package/hooks/gemini-cli/precompress.mjs +2 -2
  99. package/hooks/gemini-cli/sessionstart.mjs +12 -4
  100. package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
  101. package/hooks/jetbrains-copilot/precompact.mjs +54 -0
  102. package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
  103. package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
  104. package/hooks/kiro/posttooluse.mjs +6 -7
  105. package/hooks/kiro/pretooluse.mjs +3 -2
  106. package/hooks/posttooluse.mjs +8 -8
  107. package/hooks/precompact.mjs +3 -4
  108. package/hooks/pretooluse.mjs +5 -4
  109. package/hooks/routing-block.mjs +35 -33
  110. package/hooks/session-attribution.bundle.mjs +1 -0
  111. package/hooks/session-db.bundle.mjs +27 -8
  112. package/hooks/session-extract.bundle.mjs +2 -1
  113. package/hooks/session-helpers.mjs +44 -3
  114. package/hooks/session-loaders.mjs +37 -0
  115. package/hooks/sessionstart.mjs +5 -5
  116. package/hooks/userpromptsubmit.mjs +26 -9
  117. package/hooks/vscode-copilot/posttooluse.mjs +8 -8
  118. package/hooks/vscode-copilot/precompact.mjs +2 -2
  119. package/hooks/vscode-copilot/pretooluse.mjs +3 -2
  120. package/hooks/vscode-copilot/sessionstart.mjs +2 -2
  121. package/insight/server.mjs +237 -25
  122. package/insight/src/lib/api.ts +2 -1
  123. package/insight/src/routes/index.tsx +16 -3
  124. package/insight/src/routes/search.tsx +1 -1
  125. package/openclaw.plugin.json +1 -1
  126. package/package.json +11 -2
  127. package/server.bundle.mjs +94 -80
  128. 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
- * Implements HookAdapter for VS Code Copilot's JSON stdin/stdout hook paradigm.
5
- *
6
- * VS Code Copilot hook specifics:
7
- * - I/O: JSON on stdin, JSON on stdout (same paradigm as Claude Code)
8
- * - Hook names: PreToolUse, PostToolUse, PreCompact, SessionStart (PascalCase)
9
- * - Additional hooks: Stop, SubagentStart, SubagentStop (unique to VS Code)
10
- * - Arg modification: `updatedInput` in hookSpecificOutput wrapper (NOT flat)
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 { createHash } from "node:crypto";
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
- paradigm = "json-stdio";
38
- capabilities = {
39
- preToolUse: true,
40
- postToolUse: true,
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
- // ── Input parsing ──────────────────────────────────────
48
- parsePreToolUseInput(raw) {
49
- const input = raw;
50
- return {
51
- toolName: input.tool_name ?? "",
52
- toolInput: input.tool_input ?? {},
53
- sessionId: this.extractSessionId(input),
54
- projectDir: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
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
- // ── Configuration ──────────────────────────────────────
168
- getSettingsPath() {
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 { createHash } from "node:crypto";
14
- import { readFileSync, writeFileSync, mkdirSync, copyFileSync, accessSync, constants, } from "node:fs";
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
@@ -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).
@@ -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
- * Default parent liveness check.
14
- * Compares current ppid against the original — if it changed (reparented to
15
- * init/launchd/systemd), parent is dead. This is more reliable than
16
- * kill(ppid, 0) which succeeds for PID 1 on all platforms.
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
- * On Windows, ppid becomes 0 when parent exits.
45
+ * Exported for unit-testing with injected readers. Production code uses
46
+ * {@link defaultIsParentAlive} (captured once at module load).
19
47
  */
20
- const originalPpid = process.ppid;
21
- function defaultIsParentAlive() {
22
- const ppid = process.ppid;
23
- if (ppid !== originalPpid)
24
- return false;
25
- if (ppid === 0 || ppid === 1)
26
- return false;
27
- return true;
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
- * Loaded by OpenCode via: import("context-mode/plugin").ContextModePlugin(ctx)
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 context passed to the factory function. */
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
- * OpenCode plugin factory. Called once when OpenCode loads the plugin.
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 declare const ContextModePlugin: (ctx: PluginContext) => Promise<{
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
- export {};
70
+ declare const _default: {
71
+ server: typeof createContextModePlugin;
72
+ };
73
+ export default _default;
74
+ export { createContextModePlugin as ContextModePlugin };