context-mode 1.0.121 → 1.0.122

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 (53) 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/codex/hooks.d.ts +19 -0
  10. package/build/adapters/codex/hooks.js +22 -0
  11. package/build/adapters/codex/index.js +8 -1
  12. package/build/adapters/copilot-base.d.ts +17 -1
  13. package/build/adapters/copilot-base.js +18 -2
  14. package/build/adapters/cursor/hooks.d.ts +14 -1
  15. package/build/adapters/cursor/hooks.js +14 -0
  16. package/build/adapters/detect.d.ts +12 -2
  17. package/build/adapters/detect.js +70 -3
  18. package/build/adapters/gemini-cli/hooks.d.ts +16 -0
  19. package/build/adapters/gemini-cli/hooks.js +19 -0
  20. package/build/adapters/gemini-cli/index.js +4 -2
  21. package/build/adapters/kiro/hooks.d.ts +16 -1
  22. package/build/adapters/kiro/hooks.js +19 -0
  23. package/build/adapters/pi/extension.d.ts +9 -0
  24. package/build/adapters/pi/extension.js +47 -0
  25. package/build/adapters/qwen-code/hooks.d.ts +26 -0
  26. package/build/adapters/qwen-code/hooks.js +29 -0
  27. package/build/adapters/qwen-code/index.js +6 -0
  28. package/build/cli.js +26 -1
  29. package/build/executor.js +18 -3
  30. package/build/lifecycle.d.ts +15 -0
  31. package/build/lifecycle.js +24 -1
  32. package/build/runtime.js +34 -13
  33. package/build/session/extract.js +150 -48
  34. package/build/session/snapshot.js +46 -0
  35. package/cli.bundle.mjs +137 -136
  36. package/configs/codex/hooks.json +1 -1
  37. package/configs/cursor/hooks.json +1 -1
  38. package/configs/kiro/agent.json +1 -1
  39. package/hooks/core/routing.mjs +56 -1
  40. package/hooks/cursor/hooks.json +1 -1
  41. package/hooks/ensure-deps.mjs +22 -3
  42. package/hooks/hooks.json +9 -0
  43. package/hooks/routing-block.mjs +5 -0
  44. package/hooks/session-extract.bundle.mjs +2 -2
  45. package/hooks/session-snapshot.bundle.mjs +21 -20
  46. package/openclaw.plugin.json +1 -1
  47. package/package.json +3 -3
  48. package/scripts/heal-better-sqlite3.mjs +188 -10
  49. package/scripts/heal-installed-plugins.mjs +111 -0
  50. package/scripts/postinstall.mjs +18 -2
  51. package/server.bundle.mjs +111 -111
  52. package/start.mjs +14 -1
  53. 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.122"
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.122",
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.122",
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.122",
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.122",
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,
@@ -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_PROJECT_DIR"]]];
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.
@@ -148,6 +199,22 @@ export function detectPlatform(clientInfo) {
148
199
  // ── High confidence: environment variables ─────────────
149
200
  for (const [platform, vars] of PLATFORM_ENV_VARS) {
150
201
  if (vars.some((v) => process.env[v])) {
202
+ // Issue #539 belt-and-suspenders: VSCODE_PID/VSCODE_CWD are exported
203
+ // by VS Code into EVERY child process — including a Claude Code CLI
204
+ // launched from the integrated terminal. If env vars alone want to
205
+ // resolve to vscode-copilot, but ~/.claude/plugins/installed_plugins.json
206
+ // lists context-mode as a Claude Code plugin, the runtime must be
207
+ // Claude Code (VS Code Copilot has no plugin concept). The env-var
208
+ // tier above already handles the common case via CLAUDE_CODE_ENTRYPOINT
209
+ // / CLAUDE_PLUGIN_ROOT; this branch covers MCP-server-only boots where
210
+ // those vars have not propagated yet.
211
+ if (platform === "vscode-copilot" && claudeCodeHasContextModePlugin()) {
212
+ return {
213
+ platform: "claude-code",
214
+ confidence: "high",
215
+ reason: "VSCODE_PID set but ~/.claude/plugins/installed_plugins.json lists context-mode (issue #539 fallback)",
216
+ };
217
+ }
151
218
  return {
152
219
  platform,
153
220
  confidence: "high",
@@ -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. */
@@ -25,7 +25,7 @@ import { BaseAdapter } from "../base.js";
25
25
  // ─────────────────────────────────────────────────────────
26
26
  // Hook constants (re-exported from hooks.ts)
27
27
  // ─────────────────────────────────────────────────────────
28
- import { HOOK_TYPES as GEMINI_HOOK_NAMES, HOOK_SCRIPTS as GEMINI_HOOK_SCRIPTS, buildHookCommand as buildGeminiHookCommand, } from "./hooks.js";
28
+ import { HOOK_TYPES as GEMINI_HOOK_NAMES, HOOK_SCRIPTS as GEMINI_HOOK_SCRIPTS, buildHookCommand as buildGeminiHookCommand, EXTERNAL_MCP_MATCHER_PATTERN, } from "./hooks.js";
29
29
  // ─────────────────────────────────────────────────────────
30
30
  // Adapter implementation
31
31
  // ─────────────────────────────────────────────────────────
@@ -178,7 +178,9 @@ export class GeminiCLIAdapter extends BaseAdapter {
178
178
  ],
179
179
  [GEMINI_HOOK_NAMES.BEFORE_TOOL]: [
180
180
  {
181
- matcher: "run_shell_command|read_file|read_many_files|grep_search|search_file_content|web_fetch|activate_skill|mcp__plugin_context-mode",
181
+ // Gemini native tools + context-mode own MCP (both canonical and Claude
182
+ // shim prefixes) + external MCP catch-all (#529).
183
+ 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|${EXTERNAL_MCP_MATCHER_PATTERN}`,
182
184
  hooks: [
183
185
  {
184
186
  type: "command",
@@ -18,6 +18,21 @@ export declare const HOOK_TYPES: {
18
18
  };
19
19
  export type HookType = (typeof HOOK_TYPES)[keyof typeof HOOK_TYPES];
20
20
  export declare const HOOK_SCRIPTS: Record<string, string>;
21
+ /**
22
+ * Negative-lookahead matcher for external MCP tool namespaces on Kiro (#529).
23
+ *
24
+ * Kiro MCP wire shape: `@<server>/<tool>` (verified in
25
+ * hooks/core/tool-naming.mjs — context-mode's own tools surface as
26
+ * `@context-mode/<tool>`). This pattern fires PreToolUse for any external
27
+ * `@<server>/<tool>` whose server segment is NOT `context-mode`. Without it,
28
+ * large payloads from slack / telegram / gdrive / notion-style MCPs bypass
29
+ * the routing nudge and flood the model's context window — PostToolUse runs
30
+ * too late to keep the raw data out.
31
+ *
32
+ * Routing.mjs `isExternalMcpTool` is extended to recognise the `@<server>/`
33
+ * prefix shape so the routing branch returns external-MCP guidance.
34
+ */
35
+ export declare const EXTERNAL_MCP_MATCHER_PATTERN = "@(?!context-mode/)";
21
36
  /**
22
37
  * Tools that context-mode's PreToolUse hook intercepts on Kiro.
23
38
  *
@@ -26,7 +41,7 @@ export declare const HOOK_SCRIPTS: Record<string, string>;
26
41
  *
27
42
  * MCP tools surface as @context-mode/ctx_* in Kiro.
28
43
  */
29
- export declare const PRE_TOOL_USE_MATCHERS: readonly ["execute_bash", "fs_read", "@context-mode/ctx_execute", "@context-mode/ctx_execute_file", "@context-mode/ctx_batch_execute"];
44
+ export declare const PRE_TOOL_USE_MATCHERS: readonly ["execute_bash", "fs_read", "@context-mode/ctx_execute", "@context-mode/ctx_execute_file", "@context-mode/ctx_batch_execute", "@(?!context-mode/)"];
30
45
  /**
31
46
  * Combined matcher pattern for Kiro hook config (pipe-separated).
32
47
  * Used by generateHookConfig and configureAllHooks.
@@ -24,6 +24,24 @@ export const HOOK_SCRIPTS = {
24
24
  [HOOK_TYPES.AGENT_SPAWN]: "agentspawn.mjs",
25
25
  };
26
26
  // ─────────────────────────────────────────────────────────
27
+ // External MCP routing matcher (#529)
28
+ // ─────────────────────────────────────────────────────────
29
+ /**
30
+ * Negative-lookahead matcher for external MCP tool namespaces on Kiro (#529).
31
+ *
32
+ * Kiro MCP wire shape: `@<server>/<tool>` (verified in
33
+ * hooks/core/tool-naming.mjs — context-mode's own tools surface as
34
+ * `@context-mode/<tool>`). This pattern fires PreToolUse for any external
35
+ * `@<server>/<tool>` whose server segment is NOT `context-mode`. Without it,
36
+ * large payloads from slack / telegram / gdrive / notion-style MCPs bypass
37
+ * the routing nudge and flood the model's context window — PostToolUse runs
38
+ * too late to keep the raw data out.
39
+ *
40
+ * Routing.mjs `isExternalMcpTool` is extended to recognise the `@<server>/`
41
+ * prefix shape so the routing branch returns external-MCP guidance.
42
+ */
43
+ export const EXTERNAL_MCP_MATCHER_PATTERN = "@(?!context-mode/)";
44
+ // ─────────────────────────────────────────────────────────
27
45
  // PreToolUse matchers
28
46
  // ─────────────────────────────────────────────────────────
29
47
  /**
@@ -40,6 +58,7 @@ export const PRE_TOOL_USE_MATCHERS = [
40
58
  "@context-mode/ctx_execute",
41
59
  "@context-mode/ctx_execute_file",
42
60
  "@context-mode/ctx_batch_execute",
61
+ EXTERNAL_MCP_MATCHER_PATTERN,
43
62
  ];
44
63
  /**
45
64
  * Combined matcher pattern for Kiro hook config (pipe-separated).