context-mode 1.0.121 → 1.0.123

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 (55) 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 +4 -4
  6. package/build/adapters/claude-code/hooks.d.ts +16 -1
  7. package/build/adapters/claude-code/hooks.js +16 -0
  8. package/build/adapters/claude-code/index.js +2 -11
  9. package/build/adapters/client-map.js +6 -0
  10. package/build/adapters/codex/hooks.d.ts +19 -0
  11. package/build/adapters/codex/hooks.js +22 -0
  12. package/build/adapters/codex/index.js +8 -1
  13. package/build/adapters/copilot-base.d.ts +17 -1
  14. package/build/adapters/copilot-base.js +18 -2
  15. package/build/adapters/cursor/hooks.d.ts +14 -1
  16. package/build/adapters/cursor/hooks.js +14 -0
  17. package/build/adapters/detect.d.ts +12 -2
  18. package/build/adapters/detect.js +96 -13
  19. package/build/adapters/gemini-cli/hooks.d.ts +16 -0
  20. package/build/adapters/gemini-cli/hooks.js +19 -0
  21. package/build/adapters/gemini-cli/index.js +4 -2
  22. package/build/adapters/kiro/hooks.d.ts +16 -1
  23. package/build/adapters/kiro/hooks.js +19 -0
  24. package/build/adapters/pi/extension.d.ts +9 -0
  25. package/build/adapters/pi/extension.js +52 -1
  26. package/build/adapters/qwen-code/hooks.d.ts +26 -0
  27. package/build/adapters/qwen-code/hooks.js +29 -0
  28. package/build/adapters/qwen-code/index.js +6 -0
  29. package/build/cli.js +46 -5
  30. package/build/executor.js +18 -3
  31. package/build/lifecycle.d.ts +15 -0
  32. package/build/lifecycle.js +24 -1
  33. package/build/runtime.js +34 -13
  34. package/build/server.js +17 -2
  35. package/build/session/extract.js +150 -48
  36. package/build/session/snapshot.js +46 -0
  37. package/cli.bundle.mjs +151 -150
  38. package/configs/codex/hooks.json +1 -1
  39. package/configs/cursor/hooks.json +1 -1
  40. package/configs/kiro/agent.json +1 -1
  41. package/hooks/core/routing.mjs +56 -1
  42. package/hooks/cursor/hooks.json +1 -1
  43. package/hooks/ensure-deps.mjs +45 -10
  44. package/hooks/hooks.json +9 -0
  45. package/hooks/routing-block.mjs +5 -0
  46. package/hooks/session-extract.bundle.mjs +2 -2
  47. package/hooks/session-snapshot.bundle.mjs +21 -20
  48. package/openclaw.plugin.json +1 -1
  49. package/package.json +3 -3
  50. package/scripts/heal-better-sqlite3.mjs +188 -10
  51. package/scripts/heal-installed-plugins.mjs +111 -0
  52. package/scripts/postinstall.mjs +35 -9
  53. package/server.bundle.mjs +118 -118
  54. package/start.mjs +14 -1
  55. package/.mcp.json +0 -8
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code plugins by Mert Koseoğlu",
9
- "version": "1.0.121"
9
+ "version": "1.0.123"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "context-mode",
14
14
  "source": "./",
15
15
  "description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
16
- "version": "1.0.121",
16
+ "version": "1.0.123",
17
17
  "author": {
18
18
  "name": "Mert Koseoğlu"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.121",
3
+ "version": "1.0.123",
4
4
  "description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
@@ -3,7 +3,7 @@
3
3
  "name": "Context Mode",
4
4
  "kind": "tool",
5
5
  "description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
6
- "version": "1.0.121",
6
+ "version": "1.0.123",
7
7
  "sandbox": {
8
8
  "mode": "permissive",
9
9
  "filesystem_access": "full",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.121",
3
+ "version": "1.0.123",
4
4
  "description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
package/README.md CHANGED
@@ -146,7 +146,7 @@ This gives you all 11 MCP tools without automatic routing. The model can still u
146
146
  "hooks": {
147
147
  "BeforeTool": [
148
148
  {
149
- "matcher": "run_shell_command|read_file|read_many_files|grep_search|search_file_content|web_fetch|activate_skill|mcp__plugin_context-mode",
149
+ "matcher": "run_shell_command|read_file|read_many_files|grep_search|search_file_content|web_fetch|activate_skill|mcp__plugin_context-mode|mcp__context-mode|mcp__(?!.*context-mode)",
150
150
  "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli beforetool" }]
151
151
  }
152
152
  ],
@@ -570,7 +570,7 @@ Full documentation: [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md)
570
570
  ```json
571
571
  {
572
572
  "hooks": {
573
- "PreToolUse": [{ "matcher": "local_shell|shell|shell_command|exec_command|container.exec|functions\\.exec_command|Bash|Shell|apply_patch|functions\\.apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__.*__ctx_execute|mcp__.*__ctx_execute_file|mcp__.*__ctx_batch_execute|mcp__.*__ctx_fetch_and_index|mcp__.*__ctx_search|mcp__.*__ctx_index", "hooks": [{ "type": "command", "command": "context-mode hook codex pretooluse" }] }],
573
+ "PreToolUse": [{ "matcher": "local_shell|shell|shell_command|exec_command|container.exec|functions\\.exec_command|Bash|Shell|apply_patch|functions\\.apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__.*__ctx_execute|mcp__.*__ctx_execute_file|mcp__.*__ctx_batch_execute|mcp__.*__ctx_fetch_and_index|mcp__.*__ctx_search|mcp__.*__ctx_index|mcp__(?!.*context-mode)", "hooks": [{ "type": "command", "command": "context-mode hook codex pretooluse" }] }],
574
574
  "PostToolUse": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex posttooluse" }] }],
575
575
  "SessionStart": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex sessionstart" }] }],
576
576
  "PreCompact": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex precompact" }] }],
@@ -632,7 +632,7 @@ Full documentation: [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md)
632
632
  ```json
633
633
  {
634
634
  "hooks": {
635
- "PreToolUse": [{ "matcher": "run_shell_command|read_file|read_many_files|grep_search|web_fetch|agent|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code pretooluse" }] }],
635
+ "PreToolUse": [{ "matcher": "run_shell_command|read_file|read_many_files|grep_search|web_fetch|agent|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute|mcp__(?!.*context-mode)", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code pretooluse" }] }],
636
636
  "PostToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code posttooluse" }] }],
637
637
  "SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code sessionstart" }] }],
638
638
  "PreCompact": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code precompact" }] }],
@@ -731,7 +731,7 @@ Full configs: [`configs/antigravity/mcp_config.json`](configs/antigravity/mcp_co
731
731
  "description": "Context-mode hooks for context window protection",
732
732
  "hooks": {
733
733
  "preToolUse": [
734
- { "matcher": "execute_bash|fs_read|@context-mode/ctx_execute|@context-mode/ctx_execute_file|@context-mode/ctx_batch_execute", "command": "context-mode hook kiro pretooluse" }
734
+ { "matcher": "execute_bash|fs_read|@context-mode/ctx_execute|@context-mode/ctx_execute_file|@context-mode/ctx_batch_execute|@(?!context-mode/)", "command": "context-mode hook kiro pretooluse" }
735
735
  ],
736
736
  "postToolUse": [
737
737
  { "matcher": "*", "command": "context-mode hook kiro posttooluse" }
@@ -24,8 +24,23 @@ export declare const HOOK_TYPES: {
24
24
  readonly USER_PROMPT_SUBMIT: "UserPromptSubmit";
25
25
  };
26
26
  export type HookType = (typeof HOOK_TYPES)[keyof typeof HOOK_TYPES];
27
+ /**
28
+ * Negative-lookahead matcher for external MCP tool namespaces (#529).
29
+ *
30
+ * Claude Code's hook matcher engine evaluates each entry as a regex against
31
+ * the tool name. This pattern fires on any `mcp__<server>__<tool>` whose
32
+ * server segment is NOT context-mode's own (`plugin_context-mode_...`).
33
+ * Without it, large payloads from external MCPs (slack channel history,
34
+ * telegram messages, gdrive content, notion pages, …) bypass PreToolUse
35
+ * routing and flood the model's context window — PostToolUse runs too late
36
+ * to keep the raw data out.
37
+ *
38
+ * The negative lookahead prevents this entry from double-firing on
39
+ * context-mode's own ctx_* tools, which already have dedicated entries above.
40
+ */
41
+ export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__(?!plugin_context-mode_)";
27
42
  /** Tools that context-mode's PreToolUse hook intercepts. */
28
- export declare const PRE_TOOL_USE_MATCHERS: readonly ["Bash", "WebFetch", "Read", "Grep", "Agent", "mcp__plugin_context-mode_context-mode__ctx_execute", "mcp__plugin_context-mode_context-mode__ctx_execute_file", "mcp__plugin_context-mode_context-mode__ctx_batch_execute"];
43
+ export declare const PRE_TOOL_USE_MATCHERS: readonly ["Bash", "WebFetch", "Read", "Grep", "Agent", "mcp__plugin_context-mode_context-mode__ctx_execute", "mcp__plugin_context-mode_context-mode__ctx_execute_file", "mcp__plugin_context-mode_context-mode__ctx_batch_execute", "mcp__(?!plugin_context-mode_)"];
29
44
  /**
30
45
  * Combined matcher pattern for settings.json (pipe-separated).
31
46
  * Used by the upgrade command when writing a single consolidated entry.
@@ -30,6 +30,21 @@ export const HOOK_TYPES = {
30
30
  // ─────────────────────────────────────────────────────────
31
31
  // PreToolUse matchers
32
32
  // ─────────────────────────────────────────────────────────
33
+ /**
34
+ * Negative-lookahead matcher for external MCP tool namespaces (#529).
35
+ *
36
+ * Claude Code's hook matcher engine evaluates each entry as a regex against
37
+ * the tool name. This pattern fires on any `mcp__<server>__<tool>` whose
38
+ * server segment is NOT context-mode's own (`plugin_context-mode_...`).
39
+ * Without it, large payloads from external MCPs (slack channel history,
40
+ * telegram messages, gdrive content, notion pages, …) bypass PreToolUse
41
+ * routing and flood the model's context window — PostToolUse runs too late
42
+ * to keep the raw data out.
43
+ *
44
+ * The negative lookahead prevents this entry from double-firing on
45
+ * context-mode's own ctx_* tools, which already have dedicated entries above.
46
+ */
47
+ export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__(?!plugin_context-mode_)";
33
48
  /** Tools that context-mode's PreToolUse hook intercepts. */
34
49
  export const PRE_TOOL_USE_MATCHERS = [
35
50
  "Bash",
@@ -40,6 +55,7 @@ export const PRE_TOOL_USE_MATCHERS = [
40
55
  "mcp__plugin_context-mode_context-mode__ctx_execute",
41
56
  "mcp__plugin_context-mode_context-mode__ctx_execute_file",
42
57
  "mcp__plugin_context-mode_context-mode__ctx_batch_execute",
58
+ EXTERNAL_MCP_MATCHER_PATTERN,
43
59
  ];
44
60
  /**
45
61
  * Combined matcher pattern for settings.json (pipe-separated).
@@ -16,7 +16,7 @@ import { resolve, join } from "node:path";
16
16
  import { homedir } from "node:os";
17
17
  import { ClaudeCodeBaseAdapter } from "../claude-code-base.js";
18
18
  import { resolveClaudeConfigDir } from "../../util/claude-config.js";
19
- import { HOOK_TYPES, HOOK_SCRIPTS, REQUIRED_HOOKS, PRE_TOOL_USE_MATCHER_PATTERN, isContextModeHook, isAnyContextModeHook, extractHookScriptPath, buildHookCommand, } from "./hooks.js";
19
+ import { HOOK_TYPES, HOOK_SCRIPTS, REQUIRED_HOOKS, PRE_TOOL_USE_MATCHERS, PRE_TOOL_USE_MATCHER_PATTERN, isContextModeHook, isAnyContextModeHook, extractHookScriptPath, buildHookCommand, } from "./hooks.js";
20
20
  // ─────────────────────────────────────────────────────────
21
21
  // Adapter implementation
22
22
  // ─────────────────────────────────────────────────────────
@@ -68,16 +68,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
68
68
  }
69
69
  generateHookConfig(pluginRoot) {
70
70
  const preToolUseCommand = `node ${pluginRoot}/hooks/pretooluse.mjs`;
71
- const preToolUseMatchers = [
72
- "Bash",
73
- "WebFetch",
74
- "Read",
75
- "Grep",
76
- "Task",
77
- "mcp__plugin_context-mode_context-mode__ctx_execute",
78
- "mcp__plugin_context-mode_context-mode__ctx_execute_file",
79
- "mcp__plugin_context-mode_context-mode__ctx_batch_execute",
80
- ];
71
+ const preToolUseMatchers = [...PRE_TOOL_USE_MATCHERS];
81
72
  return {
82
73
  PreToolUse: preToolUseMatchers.map((matcher) => ({
83
74
  matcher,
@@ -21,6 +21,12 @@ export const CLIENT_NAME_TO_PLATFORM = {
21
21
  "Kiro CLI": "kiro",
22
22
  "Pi CLI": "pi",
23
23
  "Pi Coding Agent": "pi",
24
+ // Issue #542 — Pi rebranded to OMP. Upstream
25
+ // refs/platforms/oh-my-pi/packages/coding-agent/src/mcp/client.ts:46-49
26
+ // ships clientInfo.name = "omp-coding-agent". Resolved to the OMP
27
+ // adapter (~/.omp/, PI_CODING_AGENT_DIR). Legacy "Pi CLI" /
28
+ // "Pi Coding Agent" entries above still resolve to the pi adapter.
29
+ "omp-coding-agent": "omp",
24
30
  "Zed": "zed",
25
31
  "zed": "zed",
26
32
  "qwen-code": "qwen-code",
@@ -26,6 +26,25 @@ export declare const HOOK_TYPES: {
26
26
  readonly USER_PROMPT_SUBMIT: "UserPromptSubmit";
27
27
  readonly STOP: "Stop";
28
28
  };
29
+ /**
30
+ * Negative-lookahead matcher for external MCP tool namespaces on Codex CLI (#529).
31
+ *
32
+ * Codex CLI's hook `tool_name` payload uses `mcp__<server>__<tool>` for any
33
+ * MCP-namespaced tool — verified by configs/codex/hooks.json which already
34
+ * matches `mcp__.*__ctx_execute` style for context-mode's OWN MCP tools. This
35
+ * pattern fires PreToolUse for any external `mcp__<server>__<tool>` whose
36
+ * server segment does NOT contain `context-mode`. Without it, large payloads
37
+ * from slack / telegram / gdrive / notion-style MCPs bypass the routing nudge
38
+ * and flood the model's context — PostToolUse runs too late to keep raw data
39
+ * out.
40
+ *
41
+ * The negative lookahead `(?!.*context-mode)` covers both naming variants
42
+ * Codex sees in practice: the canonical `mcp__context-mode__ctx_*` AND the
43
+ * Claude Code plugin shim `mcp__plugin_context-mode_context-mode__ctx_*`.
44
+ * Codex own bare names (ctx_execute, local_shell, …) are not `mcp__`-prefixed
45
+ * and are unaffected.
46
+ */
47
+ export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__(?!.*context-mode)";
29
48
  /**
30
49
  * Path to the routing instructions file for Codex CLI.
31
50
  * Used as fallback routing awareness alongside hook-based enforcement.
@@ -30,6 +30,28 @@ export const HOOK_TYPES = {
30
30
  STOP: "Stop",
31
31
  };
32
32
  // ─────────────────────────────────────────────────────────
33
+ // External MCP routing matcher (#529)
34
+ // ─────────────────────────────────────────────────────────
35
+ /**
36
+ * Negative-lookahead matcher for external MCP tool namespaces on Codex CLI (#529).
37
+ *
38
+ * Codex CLI's hook `tool_name` payload uses `mcp__<server>__<tool>` for any
39
+ * MCP-namespaced tool — verified by configs/codex/hooks.json which already
40
+ * matches `mcp__.*__ctx_execute` style for context-mode's OWN MCP tools. This
41
+ * pattern fires PreToolUse for any external `mcp__<server>__<tool>` whose
42
+ * server segment does NOT contain `context-mode`. Without it, large payloads
43
+ * from slack / telegram / gdrive / notion-style MCPs bypass the routing nudge
44
+ * and flood the model's context — PostToolUse runs too late to keep raw data
45
+ * out.
46
+ *
47
+ * The negative lookahead `(?!.*context-mode)` covers both naming variants
48
+ * Codex sees in practice: the canonical `mcp__context-mode__ctx_*` AND the
49
+ * Claude Code plugin shim `mcp__plugin_context-mode_context-mode__ctx_*`.
50
+ * Codex own bare names (ctx_execute, local_shell, …) are not `mcp__`-prefixed
51
+ * and are unaffected.
52
+ */
53
+ export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__(?!.*context-mode)";
54
+ // ─────────────────────────────────────────────────────────
33
55
  // Routing instructions
34
56
  // ─────────────────────────────────────────────────────────
35
57
  /**
@@ -18,7 +18,14 @@ import { resolve, dirname, join } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
19
  import { BaseAdapter } from "../base.js";
20
20
  import { resolveCodexConfigDir } from "./paths.js";
21
- const PRE_TOOL_USE_MATCHER_PATTERN = "local_shell|shell|shell_command|exec_command|container.exec|functions\\.exec_command|Bash|Shell|apply_patch|functions\\.apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__.*__ctx_execute|mcp__.*__ctx_execute_file|mcp__.*__ctx_batch_execute|mcp__.*__ctx_fetch_and_index|mcp__.*__ctx_search|mcp__.*__ctx_index";
21
+ // PreToolUse matcher: canonical Codex tool names + context-mode own MCP tools
22
+ // (both bare and `mcp__<server>__<tool>` forms) + external MCP catch-all (#529).
23
+ // The final `mcp__(?!.*context-mode)` segment uses a negative lookahead that
24
+ // excludes any `mcp__` tool whose server segment contains `context-mode` so
25
+ // context-mode's own MCP tools (already wired by the explicit entries above)
26
+ // are not double-routed. Keep this as a single string literal — `codex.test.ts`
27
+ // drift-guard parses the source with a `"([^"]+)"` regex.
28
+ const PRE_TOOL_USE_MATCHER_PATTERN = "local_shell|shell|shell_command|exec_command|container.exec|functions\\.exec_command|Bash|Shell|apply_patch|functions\\.apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__.*__ctx_execute|mcp__.*__ctx_execute_file|mcp__.*__ctx_batch_execute|mcp__.*__ctx_fetch_and_index|mcp__.*__ctx_search|mcp__.*__ctx_index|mcp__(?!.*context-mode)";
22
29
  const CODEX_HOOK_COMMANDS = {
23
30
  PreToolUse: "context-mode hook codex pretooluse",
24
31
  PostToolUse: "context-mode hook codex posttooluse",
@@ -68,7 +68,23 @@ export declare abstract class CopilotBaseAdapter extends BaseAdapter implements
68
68
  formatPostToolUseResponse(response: PostToolUseResponse): unknown;
69
69
  formatPreCompactResponse(response: PreCompactResponse): unknown;
70
70
  formatSessionStartResponse(response: SessionStartResponse): unknown;
71
- getSettingsPath(): string;
71
+ /**
72
+ * Resolve the absolute path to the Copilot-style hook settings file.
73
+ *
74
+ * Issue #539 fix: previously this returned `resolve(".github", ...)`
75
+ * — a CWD-relative path. `doctor` (validateHooks) and `upgrade`
76
+ * (configureAllHooks) could legitimately run from different working
77
+ * directories (CLI invoked from a subdir, MCP server cwd=projectDir),
78
+ * so each saw a DIFFERENT settings path and the diagnose/repair loop
79
+ * never converged on the same file.
80
+ *
81
+ * Now anchors on `projectDir` when supplied (matching the sibling
82
+ * `getConfigDir(projectDir?: string)` signature in
83
+ * vscode-copilot/index.ts:93). Falls back to `process.cwd()` to keep
84
+ * existing callers source-compatible — the slice-5 follow-up will
85
+ * thread projectDir through `cli.ts doctor`/`upgrade`.
86
+ */
87
+ getSettingsPath(projectDir?: string): string;
72
88
  generateHookConfig(pluginRoot: string): HookRegistration;
73
89
  readSettings(): Record<string, unknown> | null;
74
90
  writeSettings(settings: Record<string, unknown>): void;
@@ -149,8 +149,24 @@ export class CopilotBaseAdapter extends BaseAdapter {
149
149
  return response.context ?? "";
150
150
  }
151
151
  // ── Configuration (shared) ─────────────────────────────
152
- getSettingsPath() {
153
- return resolve(".github", "hooks", "context-mode.json");
152
+ /**
153
+ * Resolve the absolute path to the Copilot-style hook settings file.
154
+ *
155
+ * Issue #539 fix: previously this returned `resolve(".github", ...)`
156
+ * — a CWD-relative path. `doctor` (validateHooks) and `upgrade`
157
+ * (configureAllHooks) could legitimately run from different working
158
+ * directories (CLI invoked from a subdir, MCP server cwd=projectDir),
159
+ * so each saw a DIFFERENT settings path and the diagnose/repair loop
160
+ * never converged on the same file.
161
+ *
162
+ * Now anchors on `projectDir` when supplied (matching the sibling
163
+ * `getConfigDir(projectDir?: string)` signature in
164
+ * vscode-copilot/index.ts:93). Falls back to `process.cwd()` to keep
165
+ * existing callers source-compatible — the slice-5 follow-up will
166
+ * thread projectDir through `cli.ts doctor`/`upgrade`.
167
+ */
168
+ getSettingsPath(projectDir) {
169
+ return resolve(projectDir ?? process.cwd(), ".github", "hooks", "context-mode.json");
154
170
  }
155
171
  generateHookConfig(pluginRoot) {
156
172
  const { HOOK_TYPES, buildHookCommand } = this.hookModule;
@@ -16,8 +16,21 @@ export declare const HOOK_TYPES: {
16
16
  export type HookType = (typeof HOOK_TYPES)[keyof typeof HOOK_TYPES];
17
17
  /** Map of hook types that have actual script files. */
18
18
  export declare const HOOK_SCRIPTS: Partial<Record<HookType, string>>;
19
+ /**
20
+ * Negative-lookahead matcher for external MCP tool namespaces on Cursor (#529).
21
+ *
22
+ * Cursor MCP wire shape: `MCP:<tool>` (verified in
23
+ * tests/fixtures/cursor/pretooluse-mcp.json, hooks/cursor/posttooluse.mjs:19-25).
24
+ * Context-mode's own tools surface as `MCP:ctx_<...>`. The negative lookahead
25
+ * on the `ctx_` prefix fires for every other MCP tool whose payload would
26
+ * otherwise flood the model's context window before PostToolUse can act.
27
+ *
28
+ * Routing.mjs `isExternalMcpTool` is extended to recognise the `MCP:` prefix
29
+ * so the routing branch returns external-MCP guidance instead of passthrough.
30
+ */
31
+ export declare const EXTERNAL_MCP_MATCHER_PATTERN = "MCP:(?!ctx_)";
19
32
  /** Canonical Cursor-native matchers for tools context-mode routes proactively. */
20
- export declare const PRE_TOOL_USE_MATCHERS: readonly ["Shell", "Read", "Grep", "WebFetch", "mcp_web_fetch", "mcp_fetch_tool", "Task", "MCP:ctx_execute", "MCP:ctx_execute_file", "MCP:ctx_batch_execute"];
33
+ export declare const PRE_TOOL_USE_MATCHERS: readonly ["Shell", "Read", "Grep", "WebFetch", "mcp_web_fetch", "mcp_fetch_tool", "Task", "MCP:ctx_execute", "MCP:ctx_execute_file", "MCP:ctx_batch_execute", "MCP:(?!ctx_)"];
21
34
  export declare const PRE_TOOL_USE_MATCHER_PATTERN: string;
22
35
  /** Required hooks for native Cursor support. */
23
36
  export declare const REQUIRED_HOOKS: HookType[];
@@ -21,6 +21,19 @@ export const HOOK_SCRIPTS = {
21
21
  [HOOK_TYPES.STOP]: "stop.mjs",
22
22
  [HOOK_TYPES.AFTER_AGENT_RESPONSE]: "afteragentresponse.mjs",
23
23
  };
24
+ /**
25
+ * Negative-lookahead matcher for external MCP tool namespaces on Cursor (#529).
26
+ *
27
+ * Cursor MCP wire shape: `MCP:<tool>` (verified in
28
+ * tests/fixtures/cursor/pretooluse-mcp.json, hooks/cursor/posttooluse.mjs:19-25).
29
+ * Context-mode's own tools surface as `MCP:ctx_<...>`. The negative lookahead
30
+ * on the `ctx_` prefix fires for every other MCP tool whose payload would
31
+ * otherwise flood the model's context window before PostToolUse can act.
32
+ *
33
+ * Routing.mjs `isExternalMcpTool` is extended to recognise the `MCP:` prefix
34
+ * so the routing branch returns external-MCP guidance instead of passthrough.
35
+ */
36
+ export const EXTERNAL_MCP_MATCHER_PATTERN = "MCP:(?!ctx_)";
24
37
  /** Canonical Cursor-native matchers for tools context-mode routes proactively. */
25
38
  // NOTE (Cursor-3, deferred): Cursor is closed-source and does not currently
26
39
  // publish the exact tool name it uses for sub-agent dispatch (the analogue of
@@ -40,6 +53,7 @@ export const PRE_TOOL_USE_MATCHERS = [
40
53
  "MCP:ctx_execute",
41
54
  "MCP:ctx_execute_file",
42
55
  "MCP:ctx_batch_execute",
56
+ EXTERNAL_MCP_MATCHER_PATTERN,
43
57
  ];
44
58
  export const PRE_TOOL_USE_MATCHER_PATTERN = PRE_TOOL_USE_MATCHERS.join("|");
45
59
  /** Required hooks for native Cursor support. */
@@ -7,7 +7,8 @@
7
7
  * 3. Fallback to Claude Code (low confidence — most common)
8
8
  *
9
9
  * Verified env vars per platform (from source code audit):
10
- * - Claude Code: CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
10
+ * - Claude Code: CLAUDE_CODE_ENTRYPOINT, CLAUDE_PLUGIN_ROOT,
11
+ * CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
11
12
  * - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
12
13
  * - KiloCode: KILO, KILO_PID | ~/.config/kilo/
13
14
  * - OpenCode: OPENCODE, OPENCODE_PID | ~/.config/opencode/
@@ -18,12 +19,21 @@
18
19
  * - JetBrains Copilot: IDEA_INITIAL_DIRECTORY, IDEA_HOME, JETBRAINS_CLIENT_ID | ~/.config/JetBrains/
19
20
  */
20
21
  import type { PlatformId, DetectionSignal, HookAdapter } from "./types.js";
22
+ /** Test-only: reset the installed_plugins.json memo so each test starts cold. */
23
+ export declare function __resetClaudeCodePluginCacheForTests(): void;
24
+ /**
25
+ * Test-only: pretend installed_plugins.json does not exist (or has no
26
+ * context-mode entry). Lets tests that exercise the genuine vscode-copilot
27
+ * env-var path run on a developer machine that actually has context-mode
28
+ * installed as a Claude Code plugin.
29
+ */
30
+ export declare function __seedClaudeCodePluginCacheMissForTests(): void;
21
31
  /**
22
32
  * High-confidence env vars per platform, checked in priority order.
23
33
  * Single source of truth — consumed by detectPlatform() below and by
24
34
  * tests that need to clear platform-related env vars deterministically.
25
35
  */
26
- export declare const PLATFORM_ENV_VARS: readonly [readonly ["claude-code", readonly ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]], readonly ["antigravity", readonly ["ANTIGRAVITY_CLI_ALIAS"]], readonly ["cursor", readonly ["CURSOR_TRACE_ID", "CURSOR_CLI"]], readonly ["kilo", readonly ["KILO", "KILO_PID"]], readonly ["opencode", readonly ["OPENCODE", "OPENCODE_PID"]], readonly ["zed", readonly ["ZED_SESSION_ID", "ZED_TERM"]], readonly ["codex", readonly ["CODEX_THREAD_ID", "CODEX_CI"]], readonly ["gemini-cli", readonly ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]], readonly ["vscode-copilot", readonly ["VSCODE_PID", "VSCODE_CWD"]], readonly ["jetbrains-copilot", readonly ["IDEA_INITIAL_DIRECTORY"]], readonly ["qwen-code", readonly ["QWEN_PROJECT_DIR"]], readonly ["omp", readonly ["PI_CODING_AGENT_DIR"]], readonly ["pi", readonly ["PI_PROJECT_DIR"]]];
36
+ export declare const PLATFORM_ENV_VARS: readonly [readonly ["claude-code", readonly ["CLAUDE_CODE_ENTRYPOINT", "CLAUDE_PLUGIN_ROOT", "CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]], readonly ["antigravity", readonly ["ANTIGRAVITY_CLI_ALIAS"]], readonly ["cursor", readonly ["CURSOR_TRACE_ID", "CURSOR_CLI"]], readonly ["kilo", readonly ["KILO", "KILO_PID"]], readonly ["opencode", readonly ["OPENCODE", "OPENCODE_PID"]], readonly ["zed", readonly ["ZED_SESSION_ID", "ZED_TERM"]], readonly ["codex", readonly ["CODEX_THREAD_ID", "CODEX_CI"]], readonly ["gemini-cli", readonly ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]], readonly ["vscode-copilot", readonly ["VSCODE_PID", "VSCODE_CWD"]], readonly ["jetbrains-copilot", readonly ["IDEA_INITIAL_DIRECTORY"]], readonly ["qwen-code", readonly ["QWEN_PROJECT_DIR"]], readonly ["omp", readonly ["PI_CODING_AGENT_DIR"]], readonly ["pi", readonly ["PI_CONFIG_DIR", "PI_SESSION_FILE", "PI_COMPILED"]]];
27
37
  /**
28
38
  * Sync map from platform identifier → home-relative path segments where that
29
39
  * platform stores its config. Mirrors the `super([...])` argument passed by
@@ -7,7 +7,8 @@
7
7
  * 3. Fallback to Claude Code (low confidence — most common)
8
8
  *
9
9
  * Verified env vars per platform (from source code audit):
10
- * - Claude Code: CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
10
+ * - Claude Code: CLAUDE_CODE_ENTRYPOINT, CLAUDE_PLUGIN_ROOT,
11
+ * CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
11
12
  * - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
12
13
  * - KiloCode: KILO, KILO_PID | ~/.config/kilo/
13
14
  * - OpenCode: OPENCODE, OPENCODE_PID | ~/.config/opencode/
@@ -17,10 +18,45 @@
17
18
  * - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
18
19
  * - JetBrains Copilot: IDEA_INITIAL_DIRECTORY, IDEA_HOME, JETBRAINS_CLIENT_ID | ~/.config/JetBrains/
19
20
  */
20
- import { existsSync } from "node:fs";
21
+ import { existsSync, readFileSync } from "node:fs";
21
22
  import { resolve } from "node:path";
22
23
  import { homedir } from "node:os";
23
24
  import { CLIENT_NAME_TO_PLATFORM } from "./client-map.js";
25
+ let claudeCodePluginCache = null;
26
+ function claudeCodeHasContextModePlugin() {
27
+ if (claudeCodePluginCache !== null) {
28
+ return claudeCodePluginCache !== "miss" && claudeCodePluginCache.hasCM;
29
+ }
30
+ try {
31
+ const path = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
32
+ const raw = readFileSync(path, "utf-8");
33
+ const parsed = JSON.parse(raw);
34
+ const keys = [
35
+ ...Object.keys(parsed.plugins ?? {}),
36
+ ...Object.keys(parsed.enabledPlugins ?? {}),
37
+ ];
38
+ const hasCM = keys.some((k) => k.includes("context-mode"));
39
+ claudeCodePluginCache = { hasCM };
40
+ return hasCM;
41
+ }
42
+ catch {
43
+ claudeCodePluginCache = "miss";
44
+ return false;
45
+ }
46
+ }
47
+ /** Test-only: reset the installed_plugins.json memo so each test starts cold. */
48
+ export function __resetClaudeCodePluginCacheForTests() {
49
+ claudeCodePluginCache = null;
50
+ }
51
+ /**
52
+ * Test-only: pretend installed_plugins.json does not exist (or has no
53
+ * context-mode entry). Lets tests that exercise the genuine vscode-copilot
54
+ * env-var path run on a developer machine that actually has context-mode
55
+ * installed as a Claude Code plugin.
56
+ */
57
+ export function __seedClaudeCodePluginCacheMissForTests() {
58
+ claudeCodePluginCache = "miss";
59
+ }
24
60
  /**
25
61
  * High-confidence env vars per platform, checked in priority order.
26
62
  * Single source of truth — consumed by detectPlatform() below and by
@@ -30,7 +66,22 @@ export const PLATFORM_ENV_VARS = [
30
66
  // Order matters: forks listed BEFORE the fork's parent so collision
31
67
  // detection works. Every entry verified against platform's own runtime
32
68
  // source code (PR #376 follow-up: full audit, May 2026 — see git blame).
33
- ["claude-code", ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]],
69
+ // Claude Code — verified against a live `env` dump (2026-05-11):
70
+ // CLAUDE_CODE_ENTRYPOINT=cli (set on every CC session)
71
+ // CLAUDE_PLUGIN_ROOT=/Users/.../<version> (set when a plugin is loaded)
72
+ // CLAUDE_PROJECT_DIR=/Users/.../project (set in hooks context)
73
+ // CLAUDE_SESSION_ID=<uuid> (legacy session marker)
74
+ // CLAUDE_CODE_ENTRYPOINT and CLAUDE_PLUGIN_ROOT are CC-exclusive — they
75
+ // are the disambiguators for issue #539 (Claude Code running inside a
76
+ // VS Code integrated terminal that has VSCODE_PID set). They MUST be
77
+ // checked here so detect resolves to claude-code BEFORE falling through
78
+ // to vscode-copilot at line 70 below.
79
+ ["claude-code", [
80
+ "CLAUDE_CODE_ENTRYPOINT",
81
+ "CLAUDE_PLUGIN_ROOT",
82
+ "CLAUDE_PROJECT_DIR",
83
+ "CLAUDE_SESSION_ID",
84
+ ]],
34
85
  // antigravity (Electron/VSCode fork) — google-gemini/gemini-cli
35
86
  // packages/core/src/ide/detect-ide.ts checks ANTIGRAVITY_CLI_ALIAS as the
36
87
  // canonical Antigravity marker. Listed before vscode-copilot.
@@ -67,9 +118,15 @@ export const PLATFORM_ENV_VARS = [
67
118
  // agent-dir override per `packages/utils/src/dirs.ts:193`. Listed
68
119
  // BEFORE pi so OMP is not misclassified as Pi when both are installed.
69
120
  ["omp", ["PI_CODING_AGENT_DIR"]],
70
- // pi — PI_PROJECT_DIR consumed by src/adapters/pi/extension.ts:154 + src/server.ts:153
71
- // implies the Pi runtime sets it before invoking the extension.
72
- ["pi", ["PI_PROJECT_DIR"]],
121
+ // pi — Issue #542 marker correction. PI_PROJECT_DIR is a consumer-set
122
+ // var (read by src/adapters/pi/extension.ts) but is NOT auto-set by
123
+ // the Pi runtime — verified against
124
+ // refs/platforms/oh-my-pi/packages/coding-agent/src/mcp/transports/stdio.ts:55-63
125
+ // (env passthrough only, no synthesis). The Pi runtime DOES set
126
+ // PI_CONFIG_DIR (config dir override), PI_SESSION_FILE (active session
127
+ // path), and PI_COMPILED (binary build marker). PI_CODING_AGENT_DIR is
128
+ // owned by OMP above; keep it there.
129
+ ["pi", ["PI_CONFIG_DIR", "PI_SESSION_FILE", "PI_COMPILED"]],
73
130
  // openclaw — removed (runtime never sets OPENCLAW_HOME or OPENCLAW_CLI;
74
131
  // detection falls through to ~/.openclaw/ config-dir tier below).
75
132
  // kiro — not listed (no auto-set process env vars; ~/.kiro/ config-dir tier).
@@ -148,6 +205,22 @@ export function detectPlatform(clientInfo) {
148
205
  // ── High confidence: environment variables ─────────────
149
206
  for (const [platform, vars] of PLATFORM_ENV_VARS) {
150
207
  if (vars.some((v) => process.env[v])) {
208
+ // Issue #539 belt-and-suspenders: VSCODE_PID/VSCODE_CWD are exported
209
+ // by VS Code into EVERY child process — including a Claude Code CLI
210
+ // launched from the integrated terminal. If env vars alone want to
211
+ // resolve to vscode-copilot, but ~/.claude/plugins/installed_plugins.json
212
+ // lists context-mode as a Claude Code plugin, the runtime must be
213
+ // Claude Code (VS Code Copilot has no plugin concept). The env-var
214
+ // tier above already handles the common case via CLAUDE_CODE_ENTRYPOINT
215
+ // / CLAUDE_PLUGIN_ROOT; this branch covers MCP-server-only boots where
216
+ // those vars have not propagated yet.
217
+ if (platform === "vscode-copilot" && claudeCodeHasContextModePlugin()) {
218
+ return {
219
+ platform: "claude-code",
220
+ confidence: "high",
221
+ reason: "VSCODE_PID set but ~/.claude/plugins/installed_plugins.json lists context-mode (issue #539 fallback)",
222
+ };
223
+ }
151
224
  return {
152
225
  platform,
153
226
  confidence: "high",
@@ -178,13 +251,15 @@ export function detectPlatform(clientInfo) {
178
251
  reason: "~/.codex/ directory exists",
179
252
  };
180
253
  }
181
- if (existsSync(resolve(home, ".cursor"))) {
182
- return {
183
- platform: "cursor",
184
- confidence: "medium",
185
- reason: "~/.cursor/ directory exists",
186
- };
187
- }
254
+ // Issue #542 — CLI agents BEFORE host IDEs.
255
+ //
256
+ // Cursor (a VSCode fork) is the most installed editor across our user
257
+ // base. Checking ~/.cursor/ first means every CLI agent co-installed
258
+ // with Cursor (Pi, OMP, Kiro, Qwen) silently routes through
259
+ // CursorAdapter even though the agent owns the session — Cursor merely
260
+ // hosts the terminal. Reorder: agents (.kiro/.omp/.pi/.qwen/.openclaw)
261
+ // win the medium-confidence tier, editors (~/.cursor/, ~/.vscode/,
262
+ // JetBrains) lose. Verified by the detect-config-dir.test.ts matrix.
188
263
  if (existsSync(resolve(home, ".kiro"))) {
189
264
  return {
190
265
  platform: "kiro",
@@ -221,6 +296,14 @@ export function detectPlatform(clientInfo) {
221
296
  reason: "~/.openclaw/ directory exists",
222
297
  };
223
298
  }
299
+ // Cursor / host IDEs — checked AFTER all CLI agents (issue #542).
300
+ if (existsSync(resolve(home, ".cursor"))) {
301
+ return {
302
+ platform: "cursor",
303
+ confidence: "medium",
304
+ reason: "~/.cursor/ directory exists",
305
+ };
306
+ }
224
307
  if (existsSync(resolve(home, ".config", "kilo"))) {
225
308
  return {
226
309
  platform: "kilo",
@@ -27,6 +27,22 @@ export declare const HOOK_TYPES: {
27
27
  readonly SESSION_START: "SessionStart";
28
28
  };
29
29
  export type HookType = (typeof HOOK_TYPES)[keyof typeof HOOK_TYPES];
30
+ /**
31
+ * Negative-lookahead matcher for external MCP tool namespaces on Gemini CLI (#529).
32
+ *
33
+ * Gemini CLI MCP wire shape: `mcp__<server>__<tool>` (verified in
34
+ * hooks/core/tool-naming.mjs — context-mode's own tools surface as
35
+ * `mcp__context-mode__<tool>`). This pattern fires BeforeTool for any
36
+ * external `mcp__<server>__<tool>` whose server segment does NOT contain
37
+ * `context-mode`. Without it, large payloads from slack / telegram / gdrive /
38
+ * notion-style MCPs bypass the routing nudge and flood the model's context.
39
+ *
40
+ * The negative lookahead `(?!.*context-mode)` covers both the canonical
41
+ * `mcp__context-mode__*` and any Claude shim `mcp__plugin_context-mode_*`
42
+ * names. Gemini native bare tool names (run_shell_command, read_file, …)
43
+ * are not `mcp__`-prefixed and are unaffected.
44
+ */
45
+ export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__(?!.*context-mode)";
30
46
  /** Map of hook types to their script file names. */
31
47
  export declare const HOOK_SCRIPTS: Record<HookType, string>;
32
48
  /** Required hooks that must be configured for context-mode to function. */
@@ -31,6 +31,25 @@ export const HOOK_TYPES = {
31
31
  SESSION_START: "SessionStart",
32
32
  };
33
33
  // ─────────────────────────────────────────────────────────
34
+ // External MCP routing matcher (#529)
35
+ // ─────────────────────────────────────────────────────────
36
+ /**
37
+ * Negative-lookahead matcher for external MCP tool namespaces on Gemini CLI (#529).
38
+ *
39
+ * Gemini CLI MCP wire shape: `mcp__<server>__<tool>` (verified in
40
+ * hooks/core/tool-naming.mjs — context-mode's own tools surface as
41
+ * `mcp__context-mode__<tool>`). This pattern fires BeforeTool for any
42
+ * external `mcp__<server>__<tool>` whose server segment does NOT contain
43
+ * `context-mode`. Without it, large payloads from slack / telegram / gdrive /
44
+ * notion-style MCPs bypass the routing nudge and flood the model's context.
45
+ *
46
+ * The negative lookahead `(?!.*context-mode)` covers both the canonical
47
+ * `mcp__context-mode__*` and any Claude shim `mcp__plugin_context-mode_*`
48
+ * names. Gemini native bare tool names (run_shell_command, read_file, …)
49
+ * are not `mcp__`-prefixed and are unaffected.
50
+ */
51
+ export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__(?!.*context-mode)";
52
+ // ─────────────────────────────────────────────────────────
34
53
  // Hook script file names
35
54
  // ─────────────────────────────────────────────────────────
36
55
  /** Map of hook types to their script file names. */