context-mode 1.0.88 → 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 (132) 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 +27 -141
  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 +113 -47
  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/pi-extension.js +24 -7
  54. package/build/runtime.js +24 -9
  55. package/build/security.d.ts +17 -1
  56. package/build/security.js +40 -6
  57. package/build/server.js +129 -21
  58. package/build/session/analytics.d.ts +8 -7
  59. package/build/session/analytics.js +95 -75
  60. package/build/session/db.d.ts +10 -1
  61. package/build/session/db.js +67 -8
  62. package/build/session/extract.js +10 -2
  63. package/build/session/project-attribution.d.ts +73 -0
  64. package/build/session/project-attribution.js +231 -0
  65. package/build/store.d.ts +7 -0
  66. package/build/store.js +117 -18
  67. package/build/truncate.d.ts +6 -0
  68. package/build/truncate.js +51 -29
  69. package/build/types.d.ts +8 -0
  70. package/cli.bundle.mjs +157 -136
  71. package/configs/antigravity/GEMINI.md +31 -36
  72. package/configs/claude-code/CLAUDE.md +31 -37
  73. package/configs/codex/AGENTS.md +35 -49
  74. package/configs/cursor/context-mode.mdc +24 -25
  75. package/configs/gemini-cli/GEMINI.md +30 -36
  76. package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
  77. package/configs/jetbrains-copilot/hooks.json +16 -0
  78. package/configs/jetbrains-copilot/mcp.json +8 -0
  79. package/configs/kilo/AGENTS.md +30 -36
  80. package/configs/kiro/KIRO.md +30 -36
  81. package/configs/kiro/agent.json +1 -1
  82. package/configs/openclaw/AGENTS.md +30 -36
  83. package/configs/opencode/AGENTS.md +30 -36
  84. package/configs/pi/AGENTS.md +31 -36
  85. package/configs/qwen-code/QWEN.md +63 -0
  86. package/configs/vscode-copilot/copilot-instructions.md +30 -36
  87. package/configs/zed/AGENTS.md +31 -36
  88. package/hooks/codex/posttooluse.mjs +7 -7
  89. package/hooks/codex/pretooluse.mjs +3 -3
  90. package/hooks/codex/sessionstart.mjs +2 -1
  91. package/hooks/core/formatters.mjs +24 -0
  92. package/hooks/core/routing.mjs +40 -15
  93. package/hooks/core/tool-naming.mjs +2 -0
  94. package/hooks/cursor/posttooluse.mjs +7 -7
  95. package/hooks/cursor/pretooluse.mjs +3 -3
  96. package/hooks/cursor/sessionstart.mjs +2 -1
  97. package/hooks/cursor/stop.mjs +2 -2
  98. package/hooks/ensure-deps.mjs +22 -10
  99. package/hooks/gemini-cli/aftertool.mjs +8 -8
  100. package/hooks/gemini-cli/beforetool.mjs +3 -2
  101. package/hooks/gemini-cli/precompress.mjs +2 -2
  102. package/hooks/gemini-cli/sessionstart.mjs +12 -4
  103. package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
  104. package/hooks/jetbrains-copilot/precompact.mjs +54 -0
  105. package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
  106. package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
  107. package/hooks/kiro/posttooluse.mjs +6 -7
  108. package/hooks/kiro/pretooluse.mjs +3 -2
  109. package/hooks/posttooluse.mjs +8 -8
  110. package/hooks/precompact.mjs +3 -4
  111. package/hooks/pretooluse.mjs +43 -20
  112. package/hooks/routing-block.mjs +35 -33
  113. package/hooks/session-attribution.bundle.mjs +1 -0
  114. package/hooks/session-db.bundle.mjs +27 -8
  115. package/hooks/session-extract.bundle.mjs +2 -1
  116. package/hooks/session-helpers.mjs +44 -3
  117. package/hooks/session-loaders.mjs +37 -0
  118. package/hooks/session-snapshot.bundle.mjs +14 -14
  119. package/hooks/sessionstart.mjs +5 -5
  120. package/hooks/userpromptsubmit.mjs +26 -9
  121. package/hooks/vscode-copilot/posttooluse.mjs +8 -8
  122. package/hooks/vscode-copilot/precompact.mjs +2 -2
  123. package/hooks/vscode-copilot/pretooluse.mjs +3 -2
  124. package/hooks/vscode-copilot/sessionstart.mjs +2 -2
  125. package/insight/server.mjs +262 -32
  126. package/insight/src/lib/api.ts +2 -1
  127. package/insight/src/routes/index.tsx +16 -3
  128. package/insight/src/routes/search.tsx +1 -1
  129. package/openclaw.plugin.json +1 -1
  130. package/package.json +11 -2
  131. package/server.bundle.mjs +117 -99
  132. package/skills/ctx-insight/SKILL.md +1 -1
@@ -17,16 +17,19 @@
17
17
  * - clientInfo.name: https://github.com/kirodotdev/Kiro/issues/5205 ("Kiro CLI")
18
18
  * - CLI hooks: https://kiro.dev/docs/cli/custom-agents/configuration-reference#hooks-field
19
19
  */
20
- import { createHash } from "node:crypto";
21
- import { readFileSync, writeFileSync, mkdirSync, copyFileSync, accessSync, constants, } from "node:fs";
22
- import { resolve, join, dirname } from "node:path";
20
+ import { readFileSync, writeFileSync, mkdirSync, } from "node:fs";
21
+ import { resolve, dirname } from "node:path";
23
22
  import { fileURLToPath } from "node:url";
24
23
  import { homedir } from "node:os";
25
- import { HOOK_TYPES as KIRO_HOOK_TYPES, buildHookCommand as buildKiroHookCommand, isContextModeHook as isKiroContextModeHook, } from "./hooks.js";
24
+ import { BaseAdapter } from "../base.js";
25
+ import { HOOK_TYPES as KIRO_HOOK_TYPES, PRE_TOOL_USE_MATCHER_PATTERN as KIRO_PRE_TOOL_USE_MATCHER_PATTERN, buildHookCommand as buildKiroHookCommand, isContextModeHook as isKiroContextModeHook, } from "./hooks.js";
26
26
  // ─────────────────────────────────────────────────────────
27
27
  // Adapter implementation
28
28
  // ─────────────────────────────────────────────────────────
29
- export class KiroAdapter {
29
+ export class KiroAdapter extends BaseAdapter {
30
+ constructor() {
31
+ super([".kiro"]);
32
+ }
30
33
  name = "Kiro";
31
34
  paradigm = "json-stdio";
32
35
  capabilities = {
@@ -95,31 +98,12 @@ export class KiroAdapter {
95
98
  getSettingsPath() {
96
99
  return resolve(homedir(), ".kiro", "settings", "mcp.json");
97
100
  }
98
- getSessionDir() {
99
- const dir = join(homedir(), ".kiro", "context-mode", "sessions");
100
- mkdirSync(dir, { recursive: true });
101
- return dir;
102
- }
103
- getSessionDBPath(projectDir) {
104
- const hash = createHash("sha256")
105
- .update(projectDir)
106
- .digest("hex")
107
- .slice(0, 16);
108
- return join(this.getSessionDir(), `${hash}.db`);
109
- }
110
- getSessionEventsPath(projectDir) {
111
- const hash = createHash("sha256")
112
- .update(projectDir)
113
- .digest("hex")
114
- .slice(0, 16);
115
- return join(this.getSessionDir(), `${hash}-events.md`);
116
- }
117
101
  generateHookConfig(pluginRoot) {
118
102
  // Kiro CLI hook config format: { preToolUse: [{ matcher, command }] }
119
103
  // Note: This generates the entries for agent config files
120
104
  return {
121
105
  [KIRO_HOOK_TYPES.PRE_TOOL_USE]: [{
122
- matcher: "*",
106
+ matcher: KIRO_PRE_TOOL_USE_MATCHER_PATTERN,
123
107
  hooks: [{ type: "command", command: buildKiroHookCommand(KIRO_HOOK_TYPES.PRE_TOOL_USE, pluginRoot) }],
124
108
  }],
125
109
  [KIRO_HOOK_TYPES.POST_TOOL_USE]: [{
@@ -241,7 +225,7 @@ export class KiroAdapter {
241
225
  const preToolUseEntries = (hooks[KIRO_HOOK_TYPES.PRE_TOOL_USE] ?? []);
242
226
  if (!preToolUseEntries.some(e => isKiroContextModeHook(e, KIRO_HOOK_TYPES.PRE_TOOL_USE))) {
243
227
  preToolUseEntries.push({
244
- matcher: "*",
228
+ matcher: KIRO_PRE_TOOL_USE_MATCHER_PATTERN,
245
229
  command: buildKiroHookCommand(KIRO_HOOK_TYPES.PRE_TOOL_USE, pluginRoot),
246
230
  });
247
231
  hooks[KIRO_HOOK_TYPES.PRE_TOOL_USE] = preToolUseEntries;
@@ -265,18 +249,6 @@ export class KiroAdapter {
265
249
  }
266
250
  return changes;
267
251
  }
268
- backupSettings() {
269
- const settingsPath = this.getSettingsPath();
270
- try {
271
- accessSync(settingsPath, constants.R_OK);
272
- const backupPath = settingsPath + ".bak";
273
- copyFileSync(settingsPath, backupPath);
274
- return backupPath;
275
- }
276
- catch {
277
- return null;
278
- }
279
- }
280
252
  setHookPermissions(_pluginRoot) {
281
253
  return [];
282
254
  }
@@ -15,8 +15,10 @@
15
15
  * - Config: openclaw.json plugins.entries, ~/.openclaw/extensions/
16
16
  * - Session dir: ~/.openclaw/context-mode/sessions/
17
17
  */
18
+ import { BaseAdapter } from "../base.js";
18
19
  import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration } from "../types.js";
19
- export declare class OpenClawAdapter implements HookAdapter {
20
+ export declare class OpenClawAdapter extends BaseAdapter implements HookAdapter {
21
+ constructor();
20
22
  readonly name = "OpenClaw";
21
23
  readonly paradigm: HookParadigm;
22
24
  readonly capabilities: PlatformCapabilities;
@@ -29,9 +31,6 @@ export declare class OpenClawAdapter implements HookAdapter {
29
31
  formatPreCompactResponse(response: PreCompactResponse): unknown;
30
32
  formatSessionStartResponse(response: SessionStartResponse): unknown;
31
33
  getSettingsPath(): string;
32
- getSessionDir(): string;
33
- getSessionDBPath(projectDir: string): string;
34
- getSessionEventsPath(projectDir: string): string;
35
34
  generateHookConfig(_pluginRoot: string): HookRegistration;
36
35
  readSettings(): Record<string, unknown> | null;
37
36
  writeSettings(settings: Record<string, unknown>): void;
@@ -15,10 +15,10 @@
15
15
  * - Config: openclaw.json plugins.entries, ~/.openclaw/extensions/
16
16
  * - Session dir: ~/.openclaw/context-mode/sessions/
17
17
  */
18
- import { createHash } from "node:crypto";
19
- import { readFileSync, writeFileSync, mkdirSync, copyFileSync, accessSync, constants, } from "node:fs";
18
+ import { readFileSync, writeFileSync, copyFileSync, accessSync, constants, } from "node:fs";
20
19
  import { resolve, join } from "node:path";
21
20
  import { homedir } from "node:os";
21
+ import { BaseAdapter } from "../base.js";
22
22
  // ─────────────────────────────────────────────────────────
23
23
  // Hook constants (re-exported from hooks.ts)
24
24
  // ─────────────────────────────────────────────────────────
@@ -26,7 +26,10 @@ import { HOOK_EVENTS as OPENCLAW_HOOK_EVENTS } from "./hooks.js";
26
26
  // ─────────────────────────────────────────────────────────
27
27
  // Adapter implementation
28
28
  // ─────────────────────────────────────────────────────────
29
- export class OpenClawAdapter {
29
+ export class OpenClawAdapter extends BaseAdapter {
30
+ constructor() {
31
+ super([".openclaw"]);
32
+ }
30
33
  name = "OpenClaw";
31
34
  paradigm = "ts-plugin";
32
35
  capabilities = {
@@ -140,25 +143,6 @@ export class OpenClawAdapter {
140
143
  // OpenClaw uses openclaw.json in the project root or ~/.openclaw/openclaw.json
141
144
  return resolve("openclaw.json");
142
145
  }
143
- getSessionDir() {
144
- const dir = join(homedir(), ".openclaw", "context-mode", "sessions");
145
- mkdirSync(dir, { recursive: true });
146
- return dir;
147
- }
148
- getSessionDBPath(projectDir) {
149
- const hash = createHash("sha256")
150
- .update(projectDir)
151
- .digest("hex")
152
- .slice(0, 16);
153
- return join(this.getSessionDir(), `${hash}.db`);
154
- }
155
- getSessionEventsPath(projectDir) {
156
- const hash = createHash("sha256")
157
- .update(projectDir)
158
- .digest("hex")
159
- .slice(0, 16);
160
- return join(this.getSessionDir(), `${hash}-events.md`);
161
- }
162
146
  generateHookConfig(_pluginRoot) {
163
147
  // OpenClaw uses TS plugin paradigm — hooks are registered via
164
148
  // api.registerHook() in the plugin entry point, not via config files.
@@ -15,9 +15,10 @@
15
15
  * - Config: opencode.json plugin array, .opencode/plugins/*.ts
16
16
  * - Session dir: ~/.config/opencode/context-mode/sessions/
17
17
  */
18
+ import { BaseAdapter } from "../base.js";
18
19
  import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration, PlatformId } from "../types.js";
19
20
  export type AdapterPlatformType = Extract<PlatformId, "opencode" | "kilo">;
20
- export declare class OpenCodeAdapter implements HookAdapter {
21
+ export declare class OpenCodeAdapter extends BaseAdapter implements HookAdapter {
21
22
  get name(): string;
22
23
  readonly paradigm: HookParadigm;
23
24
  private settingsPath?;
@@ -35,8 +36,6 @@ export declare class OpenCodeAdapter implements HookAdapter {
35
36
  getSettingsPath(): string;
36
37
  private paths;
37
38
  getSessionDir(): string;
38
- getSessionDBPath(projectDir: string): string;
39
- getSessionEventsPath(projectDir: string): string;
40
39
  generateHookConfig(_pluginRoot: string): HookRegistration;
41
40
  readSettings(): Record<string, unknown> | null;
42
41
  writeSettings(settings: Record<string, unknown>): void;
@@ -15,7 +15,6 @@
15
15
  * - Config: opencode.json plugin array, .opencode/plugins/*.ts
16
16
  * - Session dir: ~/.config/opencode/context-mode/sessions/
17
17
  */
18
- import { createHash } from "node:crypto";
19
18
  /** Strip JSONC comments (// and /* *​/) and trailing commas for JSON.parse. */
20
19
  function stripJsonComments(str) {
21
20
  return str
@@ -26,11 +25,12 @@ function stripJsonComments(str) {
26
25
  import { readFileSync, writeFileSync, mkdirSync, copyFileSync, accessSync, constants, } from "node:fs";
27
26
  import { resolve, join } from "node:path";
28
27
  import { homedir } from "node:os";
28
+ import { BaseAdapter } from "../base.js";
29
29
  // ─────────────────────────────────────────────────────────
30
30
  // Hook constants (re-exported from hooks.ts)
31
31
  // ─────────────────────────────────────────────────────────
32
32
  import { HOOK_TYPES as OPENCODE_HOOK_NAMES } from "./hooks.js";
33
- export class OpenCodeAdapter {
33
+ export class OpenCodeAdapter extends BaseAdapter {
34
34
  get name() {
35
35
  return this.platform === "kilo" ? "KiloCode" : "OpenCode";
36
36
  }
@@ -47,6 +47,9 @@ export class OpenCodeAdapter {
47
47
  };
48
48
  platform;
49
49
  constructor(platform = "opencode") {
50
+ // sessionDirSegments unused — opencode overrides getSessionDir()
51
+ // with XDG_CONFIG_HOME / APPDATA logic
52
+ super([".config", platform]);
50
53
  this.platform = platform;
51
54
  }
52
55
  // ── Input parsing ──────────────────────────────────────
@@ -179,20 +182,6 @@ export class OpenCodeAdapter {
179
182
  mkdirSync(dir, { recursive: true });
180
183
  return dir;
181
184
  }
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
185
  generateHookConfig(_pluginRoot) {
197
186
  // OpenCode uses TS plugin paradigm — hooks are registered via plugin array
198
187
  // in opencode.json, not via command-based hook entries.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * adapters/qwen-code — Qwen Code platform adapter.
3
+ *
4
+ * Extends ClaudeCodeBaseAdapter (shared wire-protocol parse/format methods)
5
+ * with Qwen Code-specific configuration, diagnostics, and session ID logic.
6
+ *
7
+ * Differences from Claude Code:
8
+ * - Config dir: ~/.qwen/ (not ~/.claude/)
9
+ * - Env vars: QWEN_PROJECT_DIR, QWEN_SESSION_ID (not CLAUDE_*)
10
+ * - Session ID priority: session_id field first (Claude: transcript_path first)
11
+ * - No plugin registry (Qwen uses settings.json directly)
12
+ * - MCP clientInfo: qwen-cli-mcp-client-* (pattern)
13
+ * - 12 hook events (superset of Claude's 5, but context-mode uses the shared 5)
14
+ */
15
+ import { ClaudeCodeBaseAdapter, type ClaudeCodeWireInput } from "../claude-code-base.js";
16
+ import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, HookRegistration } from "../types.js";
17
+ export declare class QwenCodeAdapter extends ClaudeCodeBaseAdapter implements HookAdapter {
18
+ constructor();
19
+ readonly name = "Qwen Code";
20
+ readonly paradigm: HookParadigm;
21
+ protected readonly projectDirEnvVar = "QWEN_PROJECT_DIR";
22
+ readonly capabilities: PlatformCapabilities;
23
+ getSettingsPath(): string;
24
+ generateHookConfig(pluginRoot: string): HookRegistration;
25
+ readSettings(): Record<string, unknown> | null;
26
+ writeSettings(settings: Record<string, unknown>): void;
27
+ validateHooks(_pluginRoot: string): DiagnosticResult[];
28
+ checkPluginRegistration(): DiagnosticResult;
29
+ getInstalledVersion(): string;
30
+ configureAllHooks(_pluginRoot: string): string[];
31
+ setHookPermissions(_pluginRoot: string): string[];
32
+ updatePluginRegistry(_pluginRoot: string, _version: string): void;
33
+ getRoutingInstructionsConfig(): {
34
+ instructionsPath: string;
35
+ targetPath: string;
36
+ platformName: string;
37
+ };
38
+ protected extractSessionId(input: ClaudeCodeWireInput): string;
39
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * adapters/qwen-code — Qwen Code platform adapter.
3
+ *
4
+ * Extends ClaudeCodeBaseAdapter (shared wire-protocol parse/format methods)
5
+ * with Qwen Code-specific configuration, diagnostics, and session ID logic.
6
+ *
7
+ * Differences from Claude Code:
8
+ * - Config dir: ~/.qwen/ (not ~/.claude/)
9
+ * - Env vars: QWEN_PROJECT_DIR, QWEN_SESSION_ID (not CLAUDE_*)
10
+ * - Session ID priority: session_id field first (Claude: transcript_path first)
11
+ * - No plugin registry (Qwen uses settings.json directly)
12
+ * - MCP clientInfo: qwen-cli-mcp-client-* (pattern)
13
+ * - 12 hook events (superset of Claude's 5, but context-mode uses the shared 5)
14
+ */
15
+ import { readFileSync, } from "node:fs";
16
+ import { resolve, join } from "node:path";
17
+ import { homedir } from "node:os";
18
+ import { ClaudeCodeBaseAdapter } from "../claude-code-base.js";
19
+ // ─────────────────────────────────────────────────────────
20
+ // Adapter implementation
21
+ // ─────────────────────────────────────────────────────────
22
+ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
23
+ constructor() {
24
+ super([".qwen"]);
25
+ }
26
+ name = "Qwen Code";
27
+ paradigm = "json-stdio";
28
+ projectDirEnvVar = "QWEN_PROJECT_DIR";
29
+ capabilities = {
30
+ preToolUse: true,
31
+ postToolUse: true,
32
+ preCompact: true,
33
+ sessionStart: true,
34
+ canModifyArgs: true,
35
+ canModifyOutput: true,
36
+ canInjectSessionContext: true,
37
+ };
38
+ // ── Configuration (differs from Claude Code) ───────────
39
+ getSettingsPath() {
40
+ return resolve(homedir(), ".qwen", "settings.json");
41
+ }
42
+ generateHookConfig(pluginRoot) {
43
+ // Qwen Code passes native tool names in hook stdin (verified from
44
+ // packages/core/src/tools/tool-names.ts). Claude-style names (Bash, Read)
45
+ // are only accepted in permission configs, NOT in hook tool_name payloads.
46
+ const preToolUseMatcher = [
47
+ // Qwen-native names (canonical tool_name in hook stdin)
48
+ "run_shell_command", "read_file", "read_many_files", "grep_search",
49
+ "web_fetch", "agent",
50
+ // MCP tools (same naming convention as Claude Code)
51
+ "mcp__plugin_context-mode_context-mode__ctx_execute",
52
+ "mcp__plugin_context-mode_context-mode__ctx_execute_file",
53
+ "mcp__plugin_context-mode_context-mode__ctx_batch_execute",
54
+ ].join("|");
55
+ return {
56
+ PreToolUse: [
57
+ {
58
+ matcher: preToolUseMatcher,
59
+ hooks: [
60
+ { type: "command", command: `node ${pluginRoot}/hooks/pretooluse.mjs` },
61
+ ],
62
+ },
63
+ ],
64
+ PostToolUse: [
65
+ {
66
+ matcher: "",
67
+ hooks: [
68
+ { type: "command", command: `node ${pluginRoot}/hooks/posttooluse.mjs` },
69
+ ],
70
+ },
71
+ ],
72
+ SessionStart: [
73
+ {
74
+ matcher: "",
75
+ hooks: [
76
+ { type: "command", command: `node ${pluginRoot}/hooks/sessionstart.mjs` },
77
+ ],
78
+ },
79
+ ],
80
+ PreCompact: [
81
+ {
82
+ matcher: "",
83
+ hooks: [
84
+ { type: "command", command: `node ${pluginRoot}/hooks/precompact.mjs` },
85
+ ],
86
+ },
87
+ ],
88
+ UserPromptSubmit: [
89
+ {
90
+ matcher: "",
91
+ hooks: [
92
+ { type: "command", command: `node ${pluginRoot}/hooks/userpromptsubmit.mjs` },
93
+ ],
94
+ },
95
+ ],
96
+ };
97
+ }
98
+ // ── Settings read/write ────────────────────────────────
99
+ readSettings() {
100
+ try {
101
+ const raw = readFileSync(this.getSettingsPath(), "utf-8");
102
+ return JSON.parse(raw);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ writeSettings(settings) {
109
+ const { writeFileSync } = require("node:fs");
110
+ writeFileSync(this.getSettingsPath(), JSON.stringify(settings, null, 2));
111
+ }
112
+ // ── Diagnostics (doctor) ───────────────────────────────
113
+ validateHooks(_pluginRoot) {
114
+ const results = [];
115
+ const settings = this.readSettings();
116
+ const hooks = (settings?.hooks ?? {});
117
+ for (const hookName of ["PreToolUse", "PostToolUse", "SessionStart", "PreCompact", "UserPromptSubmit"]) {
118
+ const configured = Array.isArray(hooks[hookName]) && hooks[hookName].length > 0;
119
+ results.push({
120
+ check: `${hookName} hook`,
121
+ status: configured ? "pass" : "fail",
122
+ message: configured
123
+ ? `${hookName} hook configured in ~/.qwen/settings.json`
124
+ : `${hookName} hook not found in ~/.qwen/settings.json`,
125
+ ...(configured ? {} : { fix: `Add ${hookName} hook to ~/.qwen/settings.json` }),
126
+ });
127
+ }
128
+ return results;
129
+ }
130
+ checkPluginRegistration() {
131
+ // Qwen Code has no plugin registry — check for MCP config instead
132
+ try {
133
+ const settings = this.readSettings();
134
+ if (settings?.mcpServers && typeof settings.mcpServers === "object") {
135
+ const servers = settings.mcpServers;
136
+ if (Object.keys(servers).some(k => k.includes("context-mode"))) {
137
+ return {
138
+ check: "Plugin registration",
139
+ status: "pass",
140
+ message: "context-mode found in mcpServers",
141
+ };
142
+ }
143
+ return {
144
+ check: "Plugin registration",
145
+ status: "fail",
146
+ message: "mcpServers exists but context-mode not found",
147
+ fix: "Add context-mode to mcpServers in ~/.qwen/settings.json",
148
+ };
149
+ }
150
+ return {
151
+ check: "Plugin registration",
152
+ status: "warn",
153
+ message: "No mcpServers in ~/.qwen/settings.json",
154
+ };
155
+ }
156
+ catch {
157
+ return {
158
+ check: "Plugin registration",
159
+ status: "warn",
160
+ message: "Could not read ~/.qwen/settings.json",
161
+ };
162
+ }
163
+ }
164
+ getInstalledVersion() {
165
+ return "not installed";
166
+ }
167
+ configureAllHooks(_pluginRoot) {
168
+ return [];
169
+ }
170
+ setHookPermissions(_pluginRoot) {
171
+ return [];
172
+ }
173
+ updatePluginRegistry(_pluginRoot, _version) {
174
+ // No plugin registry in Qwen Code
175
+ }
176
+ getRoutingInstructionsConfig() {
177
+ const instructionsPath = resolve(join(homedir(), ".qwen", "QWEN.md"));
178
+ return {
179
+ instructionsPath,
180
+ targetPath: "QWEN.md",
181
+ platformName: "Qwen Code",
182
+ };
183
+ }
184
+ // ── Session ID extraction (differs from Claude Code) ───
185
+ // Qwen Code prioritizes session_id field, then QWEN_SESSION_ID env var.
186
+ // Claude Code prioritizes transcript_path UUID first.
187
+ extractSessionId(input) {
188
+ if (input.session_id)
189
+ return input.session_id;
190
+ if (input.transcript_path) {
191
+ const match = input.transcript_path.match(/([a-f0-9-]{36})\.jsonl$/);
192
+ if (match)
193
+ return match[1];
194
+ }
195
+ if (process.env.QWEN_SESSION_ID)
196
+ return process.env.QWEN_SESSION_ID;
197
+ return `pid-${process.ppid}`;
198
+ }
199
+ }
@@ -193,7 +193,7 @@ export interface DiagnosticResult {
193
193
  fix?: string;
194
194
  }
195
195
  /** Supported platform identifiers. */
196
- export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "kilo" | "openclaw" | "codex" | "vscode-copilot" | "cursor" | "antigravity" | "kiro" | "pi" | "zed" | "unknown";
196
+ export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "kilo" | "openclaw" | "codex" | "vscode-copilot" | "jetbrains-copilot" | "cursor" | "antigravity" | "kiro" | "pi" | "zed" | "qwen-code" | "unknown";
197
197
  /** Detection signal used to identify which platform is running. */
198
198
  export interface DetectionSignal {
199
199
  /** Platform identifier. */
@@ -1,56 +1,26 @@
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 type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration } from "../types.js";
25
- export declare class VSCodeCopilotAdapter implements HookAdapter {
12
+ import { CopilotBaseAdapter } from "../copilot-base.js";
13
+ import type { CopilotHookInput, CopilotHookModule } from "../copilot-base.js";
14
+ import type { DiagnosticResult } from "../types.js";
15
+ export declare class VSCodeCopilotAdapter extends CopilotBaseAdapter {
16
+ constructor();
26
17
  readonly name = "VS Code Copilot";
27
- readonly paradigm: HookParadigm;
28
- readonly capabilities: PlatformCapabilities;
29
- parsePreToolUseInput(raw: unknown): PreToolUseEvent;
30
- parsePostToolUseInput(raw: unknown): PostToolUseEvent;
31
- parsePreCompactInput(raw: unknown): PreCompactEvent;
32
- parseSessionStartInput(raw: unknown): SessionStartEvent;
33
- formatPreToolUseResponse(response: PreToolUseResponse): unknown;
34
- formatPostToolUseResponse(response: PostToolUseResponse): unknown;
35
- formatPreCompactResponse(response: PreCompactResponse): unknown;
36
- formatSessionStartResponse(response: SessionStartResponse): unknown;
37
- getSettingsPath(): string;
18
+ protected readonly hookModule: CopilotHookModule;
19
+ protected readonly hookSubdir = "vscode-copilot";
20
+ protected extractSessionId(input: CopilotHookInput): string;
21
+ protected getProjectDir(): string;
38
22
  getSessionDir(): string;
39
- getSessionDBPath(projectDir: string): string;
40
- getSessionEventsPath(projectDir: string): string;
41
- generateHookConfig(pluginRoot: string): HookRegistration;
42
- readSettings(): Record<string, unknown> | null;
43
- writeSettings(settings: Record<string, unknown>): void;
44
23
  validateHooks(pluginRoot: string): DiagnosticResult[];
45
24
  checkPluginRegistration(): DiagnosticResult;
46
25
  getInstalledVersion(): string;
47
- configureAllHooks(pluginRoot: string): string[];
48
- backupSettings(): string | null;
49
- setHookPermissions(pluginRoot: string): string[];
50
- updatePluginRegistry(_pluginRoot: string, _version: string): void;
51
- /**
52
- * Extract session ID from VS Code Copilot hook input.
53
- * VS Code Copilot uses camelCase sessionId (NOT session_id).
54
- */
55
- private extractSessionId;
56
26
  }