context-mode 1.0.123 → 1.0.125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +3 -3
- package/build/adapters/claude-code/hooks.d.ts +12 -13
- package/build/adapters/claude-code/hooks.js +11 -12
- package/build/adapters/claude-code/index.js +11 -5
- package/build/adapters/codex/hooks.d.ts +13 -14
- package/build/adapters/codex/hooks.js +13 -14
- package/build/adapters/codex/index.js +19 -8
- package/build/adapters/detect.d.ts +48 -4
- package/build/adapters/detect.js +115 -24
- package/build/adapters/opencode/plugin.js +1 -1
- package/build/adapters/pi/extension.d.ts +28 -0
- package/build/adapters/pi/extension.js +55 -5
- package/build/adapters/pi/mcp-bridge.js +15 -0
- package/build/db-base.d.ts +19 -2
- package/build/db-base.js +49 -15
- package/build/executor.js +40 -3
- package/build/runtime.d.ts +2 -1
- package/build/runtime.js +10 -0
- package/build/server.js +18 -4
- package/build/util/project-dir.d.ts +11 -0
- package/build/util/project-dir.js +38 -20
- package/cli.bundle.mjs +135 -132
- package/configs/codex/hooks.json +1 -1
- package/hooks/core/routing.mjs +8 -4
- package/hooks/hooks.json +1 -1
- package/hooks/session-db.bundle.mjs +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +95 -92
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.125"
|
|
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.
|
|
16
|
+
"version": "1.0.125",
|
|
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.
|
|
3
|
+
"version": "1.0.125",
|
|
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.
|
|
6
|
+
"version": "1.0.125",
|
|
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.
|
|
3
|
+
"version": "1.0.125",
|
|
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
|
@@ -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|
|
|
573
|
+
"PreToolUse": [{ "matcher": "local_shell|shell|shell_command|exec_command|Bash|Shell|apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__", "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" }] }],
|
|
@@ -964,7 +964,7 @@ npm install -g context-mode
|
|
|
964
964
|
| Tool | What it does | Context saved |
|
|
965
965
|
|---|---|---|
|
|
966
966
|
| `ctx_batch_execute` | Run multiple commands + search multiple queries in ONE call. Opt-in `concurrency: 1-8` for I/O-bound batches. | 986 KB → 62 KB |
|
|
967
|
-
| `ctx_execute` | Run code in
|
|
967
|
+
| `ctx_execute` | Run code in 12 languages. Only stdout enters context. | 56 KB → 299 B |
|
|
968
968
|
| `ctx_execute_file` | Process files in sandbox. Raw content never leaves. | 45 KB → 155 B |
|
|
969
969
|
| `ctx_index` | Chunk markdown into FTS5 with BM25 ranking. | 60 KB → 40 B |
|
|
970
970
|
| `ctx_search` | Query indexed content with multiple queries in one call. | On-demand retrieval |
|
|
@@ -978,7 +978,7 @@ npm install -g context-mode
|
|
|
978
978
|
|
|
979
979
|
Each `ctx_execute` call spawns an isolated subprocess with its own process boundary. Scripts can't access each other's memory or state. The subprocess runs your code, captures stdout, and only that stdout enters the conversation context. The raw data — log files, API responses, snapshots — never leaves the sandbox.
|
|
980
980
|
|
|
981
|
-
|
|
981
|
+
Twelve language runtimes are available: JavaScript, TypeScript, Python, Shell, Ruby, Go, Rust, PHP, Perl, R, Elixir, and C#. Bun is auto-detected for 3-5x faster JS/TS execution.
|
|
982
982
|
|
|
983
983
|
Authenticated CLIs work through credential passthrough — `gh`, `aws`, `gcloud`, `kubectl`, `docker` inherit environment variables and config paths without exposing them to the conversation.
|
|
984
984
|
|
|
@@ -25,22 +25,21 @@ export declare const HOOK_TYPES: {
|
|
|
25
25
|
};
|
|
26
26
|
export type HookType = (typeof HOOK_TYPES)[keyof typeof HOOK_TYPES];
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* External MCP catch-all matcher for Claude Code (#529, #547 hotfix).
|
|
29
29
|
*
|
|
30
|
-
* Claude Code's hook matcher engine
|
|
31
|
-
*
|
|
32
|
-
* server
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* context-mode's own ctx_* tools, which already have dedicated entries above.
|
|
30
|
+
* Claude Code's hook matcher engine treats this entry as a substring match
|
|
31
|
+
* (it also accepts regex, but `mcp__` alone is enough — every MCP tool
|
|
32
|
+
* surfaces as `mcp__<server>__<tool>`). v1.0.124 used a negative lookahead
|
|
33
|
+
* `mcp__(?!plugin_context-mode_)` to skip context-mode's own MCP tools,
|
|
34
|
+
* but this same hooks.json is bundled to Codex CLI which uses Rust's
|
|
35
|
+
* `regex` crate (no look-around support) — Codex rejected the matcher at
|
|
36
|
+
* boot, breaking every Codex user (#547). Drop the lookaround on both
|
|
37
|
+
* sides; the hook BODY (`isExternalMcpTool()` in hooks/core/routing.mjs)
|
|
38
|
+
* already filters context-mode's own tools, so semantics are preserved.
|
|
40
39
|
*/
|
|
41
|
-
export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__
|
|
40
|
+
export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__";
|
|
42
41
|
/** Tools that context-mode's PreToolUse hook intercepts. */
|
|
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__
|
|
42
|
+
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__"];
|
|
44
43
|
/**
|
|
45
44
|
* Combined matcher pattern for settings.json (pipe-separated).
|
|
46
45
|
* Used by the upgrade command when writing a single consolidated entry.
|
|
@@ -31,20 +31,19 @@ export const HOOK_TYPES = {
|
|
|
31
31
|
// PreToolUse matchers
|
|
32
32
|
// ─────────────────────────────────────────────────────────
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* External MCP catch-all matcher for Claude Code (#529, #547 hotfix).
|
|
35
35
|
*
|
|
36
|
-
* Claude Code's hook matcher engine
|
|
37
|
-
*
|
|
38
|
-
* server
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* context-mode's own ctx_* tools, which already have dedicated entries above.
|
|
36
|
+
* Claude Code's hook matcher engine treats this entry as a substring match
|
|
37
|
+
* (it also accepts regex, but `mcp__` alone is enough — every MCP tool
|
|
38
|
+
* surfaces as `mcp__<server>__<tool>`). v1.0.124 used a negative lookahead
|
|
39
|
+
* `mcp__(?!plugin_context-mode_)` to skip context-mode's own MCP tools,
|
|
40
|
+
* but this same hooks.json is bundled to Codex CLI which uses Rust's
|
|
41
|
+
* `regex` crate (no look-around support) — Codex rejected the matcher at
|
|
42
|
+
* boot, breaking every Codex user (#547). Drop the lookaround on both
|
|
43
|
+
* sides; the hook BODY (`isExternalMcpTool()` in hooks/core/routing.mjs)
|
|
44
|
+
* already filters context-mode's own tools, so semantics are preserved.
|
|
46
45
|
*/
|
|
47
|
-
export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__
|
|
46
|
+
export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__";
|
|
48
47
|
/** Tools that context-mode's PreToolUse hook intercepts. */
|
|
49
48
|
export const PRE_TOOL_USE_MATCHERS = [
|
|
50
49
|
"Bash",
|
|
@@ -67,7 +67,13 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
67
67
|
return join(this.getConfigDir(), "settings.json");
|
|
68
68
|
}
|
|
69
69
|
generateHookConfig(pluginRoot) {
|
|
70
|
-
|
|
70
|
+
// Paths must be double-quoted so that hosts (or our own diagnostic
|
|
71
|
+
// regex) parse them correctly when pluginRoot contains spaces — common
|
|
72
|
+
// on Windows where the user folder is e.g. "C:\Users\First Last\...".
|
|
73
|
+
// Without quotes, extractHookScriptPath's `\S+\.mjs` fallback grabs
|
|
74
|
+
// only the tail after the last space, producing a doubled-path FAIL
|
|
75
|
+
// in `ctx doctor`. Matches the quoting style used in hooks/hooks.json.
|
|
76
|
+
const preToolUseCommand = `node "${pluginRoot}/hooks/pretooluse.mjs"`;
|
|
71
77
|
const preToolUseMatchers = [...PRE_TOOL_USE_MATCHERS];
|
|
72
78
|
return {
|
|
73
79
|
PreToolUse: preToolUseMatchers.map((matcher) => ({
|
|
@@ -80,7 +86,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
80
86
|
hooks: [
|
|
81
87
|
{
|
|
82
88
|
type: "command",
|
|
83
|
-
command: `node ${pluginRoot}/hooks/posttooluse.mjs`,
|
|
89
|
+
command: `node "${pluginRoot}/hooks/posttooluse.mjs"`,
|
|
84
90
|
},
|
|
85
91
|
],
|
|
86
92
|
},
|
|
@@ -91,7 +97,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
91
97
|
hooks: [
|
|
92
98
|
{
|
|
93
99
|
type: "command",
|
|
94
|
-
command: `node ${pluginRoot}/hooks/precompact.mjs`,
|
|
100
|
+
command: `node "${pluginRoot}/hooks/precompact.mjs"`,
|
|
95
101
|
},
|
|
96
102
|
],
|
|
97
103
|
},
|
|
@@ -102,7 +108,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
102
108
|
hooks: [
|
|
103
109
|
{
|
|
104
110
|
type: "command",
|
|
105
|
-
command: `node ${pluginRoot}/hooks/userpromptsubmit.mjs`,
|
|
111
|
+
command: `node "${pluginRoot}/hooks/userpromptsubmit.mjs"`,
|
|
106
112
|
},
|
|
107
113
|
],
|
|
108
114
|
},
|
|
@@ -113,7 +119,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
113
119
|
hooks: [
|
|
114
120
|
{
|
|
115
121
|
type: "command",
|
|
116
|
-
command: `node ${pluginRoot}/hooks/sessionstart.mjs`,
|
|
122
|
+
command: `node "${pluginRoot}/hooks/sessionstart.mjs"`,
|
|
117
123
|
},
|
|
118
124
|
],
|
|
119
125
|
},
|
|
@@ -27,24 +27,23 @@ export declare const HOOK_TYPES: {
|
|
|
27
27
|
readonly STOP: "Stop";
|
|
28
28
|
};
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* External MCP catch-all matcher for Codex CLI (#529, #547 hotfix).
|
|
31
31
|
*
|
|
32
32
|
* Codex CLI's hook `tool_name` payload uses `mcp__<server>__<tool>` for any
|
|
33
|
-
* MCP-namespaced tool
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* and flood the model's context — PostToolUse runs too late to keep raw data
|
|
39
|
-
* out.
|
|
33
|
+
* MCP-namespaced tool. Originally this constant used a negative lookahead
|
|
34
|
+
* `mcp__(?!.*context-mode)` to exclude context-mode's own MCP tools at the
|
|
35
|
+
* matcher layer. v1.0.124 shipped that pattern and Codex (Rust `regex` crate)
|
|
36
|
+
* rejected the matcher at boot with "look-around not supported", breaking
|
|
37
|
+
* every Codex user (#547).
|
|
40
38
|
*
|
|
41
|
-
* The
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
39
|
+
* Fix: drop the lookaround. The matcher is now a charset-clean literal
|
|
40
|
+
* (`[A-Za-z0-9_|]` only), satisfying Codex's `is_exact_matcher`
|
|
41
|
+
* (refs/platforms/codex/codex-rs/hooks/src/events/common.rs:152) which
|
|
42
|
+
* short-circuits the regex engine entirely. context-mode's own MCP tools are
|
|
43
|
+
* already filtered in the hook BODY by `isExternalMcpTool()` in
|
|
44
|
+
* hooks/core/routing.mjs — semantics preserved.
|
|
46
45
|
*/
|
|
47
|
-
export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__
|
|
46
|
+
export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__";
|
|
48
47
|
/**
|
|
49
48
|
* Path to the routing instructions file for Codex CLI.
|
|
50
49
|
* Used as fallback routing awareness alongside hook-based enforcement.
|
|
@@ -33,24 +33,23 @@ export const HOOK_TYPES = {
|
|
|
33
33
|
// External MCP routing matcher (#529)
|
|
34
34
|
// ─────────────────────────────────────────────────────────
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* External MCP catch-all matcher for Codex CLI (#529, #547 hotfix).
|
|
37
37
|
*
|
|
38
38
|
* Codex CLI's hook `tool_name` payload uses `mcp__<server>__<tool>` for any
|
|
39
|
-
* MCP-namespaced tool
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* and flood the model's context — PostToolUse runs too late to keep raw data
|
|
45
|
-
* out.
|
|
39
|
+
* MCP-namespaced tool. Originally this constant used a negative lookahead
|
|
40
|
+
* `mcp__(?!.*context-mode)` to exclude context-mode's own MCP tools at the
|
|
41
|
+
* matcher layer. v1.0.124 shipped that pattern and Codex (Rust `regex` crate)
|
|
42
|
+
* rejected the matcher at boot with "look-around not supported", breaking
|
|
43
|
+
* every Codex user (#547).
|
|
46
44
|
*
|
|
47
|
-
* The
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
45
|
+
* Fix: drop the lookaround. The matcher is now a charset-clean literal
|
|
46
|
+
* (`[A-Za-z0-9_|]` only), satisfying Codex's `is_exact_matcher`
|
|
47
|
+
* (refs/platforms/codex/codex-rs/hooks/src/events/common.rs:152) which
|
|
48
|
+
* short-circuits the regex engine entirely. context-mode's own MCP tools are
|
|
49
|
+
* already filtered in the hook BODY by `isExternalMcpTool()` in
|
|
50
|
+
* hooks/core/routing.mjs — semantics preserved.
|
|
52
51
|
*/
|
|
53
|
-
export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__
|
|
52
|
+
export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__";
|
|
54
53
|
// ─────────────────────────────────────────────────────────
|
|
55
54
|
// Routing instructions
|
|
56
55
|
// ─────────────────────────────────────────────────────────
|
|
@@ -18,14 +18,25 @@ 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
|
-
// PreToolUse matcher: canonical Codex tool names + context-mode
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
21
|
+
// PreToolUse matcher: canonical Codex tool names + context-mode bare MCP tool
|
|
22
|
+
// names + external MCP catch-all literal (#529, #547 hotfix).
|
|
23
|
+
//
|
|
24
|
+
// Codex CLI's Rust `regex` crate does NOT support look-around, and
|
|
25
|
+
// `is_exact_matcher` (refs/platforms/codex/codex-rs/hooks/src/events/common.rs:152)
|
|
26
|
+
// short-circuits the regex engine entirely when the matcher contains only
|
|
27
|
+
// [A-Za-z0-9_|]. v1.0.124 shipped a matcher with `(?!.*context-mode)` AND
|
|
28
|
+
// `mcp__.*__ctx_*` regex syntax — Codex rejected the file at boot with
|
|
29
|
+
// "look-around not supported" → all v1.0.124 Codex users broken (#547).
|
|
30
|
+
//
|
|
31
|
+
// Fix: keep only literal tool names (charset-clean). The hook BODY already
|
|
32
|
+
// filters context-mode's own MCP tools via `isExternalMcpTool()` in
|
|
33
|
+
// hooks/core/routing.mjs, so dropping `mcp__.*__ctx_*` and the lookaround
|
|
34
|
+
// preserves end-to-end semantics. The literal `mcp__` final segment is a
|
|
35
|
+
// no-op under exact-matcher mode but kept for parity with hooks/hooks.json.
|
|
36
|
+
//
|
|
37
|
+
// Keep this as a single string literal — `codex.test.ts` drift-guard parses
|
|
38
|
+
// the source with a `"([^"]+)"` regex.
|
|
39
|
+
const PRE_TOOL_USE_MATCHER_PATTERN = "local_shell|shell|shell_command|exec_command|Bash|Shell|apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__";
|
|
29
40
|
const CODEX_HOOK_COMMANDS = {
|
|
30
41
|
PreToolUse: "context-mode hook codex pretooluse",
|
|
31
42
|
PostToolUse: "context-mode hook codex posttooluse",
|
|
@@ -29,11 +29,55 @@ export declare function __resetClaudeCodePluginCacheForTests(): void;
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function __seedClaudeCodePluginCacheMissForTests(): void;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Tag for each PLATFORM_ENV_VARS row.
|
|
33
|
+
* - `workspace`: env var names a project/working directory. Used by
|
|
34
|
+
* `resolveProjectDir({ strictPlatform })` to form the candidate list,
|
|
35
|
+
* and by Pi's bridge to scrub foreign workspace vars on child spawn.
|
|
36
|
+
* - `identification`: env var only signals which host is running; carries
|
|
37
|
+
* no project path. NEVER scrubbed (some are load-bearing, e.g.
|
|
38
|
+
* CLAUDE_PLUGIN_ROOT for hook integrations).
|
|
39
|
+
*
|
|
40
|
+
* Issue #545 — algorithmic env-leak fix. The split allows resolveProjectDir
|
|
41
|
+
* to derive ALLOW (own workspace vars) and BAN (other platforms' workspace
|
|
42
|
+
* vars) sets from a single registry, satisfying MUST-3 (15 adapters equal).
|
|
43
|
+
*/
|
|
44
|
+
export type EnvVarRole = "workspace" | "identification";
|
|
45
|
+
export interface PlatformEnvEntry {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly role: EnvVarRole;
|
|
48
|
+
/**
|
|
49
|
+
* When `false`, this entry is NOT used as a high-confidence detection
|
|
50
|
+
* signal — only consumed by `workspaceEnvVarsFor`/`foreignWorkspaceEnv`
|
|
51
|
+
* (project-dir cascade and bridge env scrub). Use for consumer-set
|
|
52
|
+
* workspace vars that the host runtime never emits itself, so that a
|
|
53
|
+
* stale env var on an unrelated host does not misclassify the platform.
|
|
54
|
+
* Default: `true` (entry participates in detection).
|
|
55
|
+
*
|
|
56
|
+
* Issue #542 — PI_PROJECT_DIR / PI_WORKSPACE_DIR are consumer-set and
|
|
57
|
+
* MUST NOT trigger Pi detection on their own.
|
|
58
|
+
*/
|
|
59
|
+
readonly detect?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export declare const PLATFORM_ENV_VARS: ReadonlyMap<PlatformId, readonly PlatformEnvEntry[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Backwards-compat shim: legacy `string[]` shape used by detection logic and
|
|
64
|
+
* by tests that iterate the registry to clear env vars. Always returns the
|
|
65
|
+
* names in registry order.
|
|
66
|
+
*/
|
|
67
|
+
export declare function getEnvVarNames(platform: PlatformId): string[];
|
|
68
|
+
/**
|
|
69
|
+
* Issue #545 — return only role=workspace env var names for a platform, in
|
|
70
|
+
* registry order. Empty array for adapters with no workspace var (e.g.
|
|
71
|
+
* codex, kilo, zed, antigravity, openclaw, kiro). Consumed by
|
|
72
|
+
* `resolveProjectDir({ strictPlatform })` to build the cascade.
|
|
73
|
+
*/
|
|
74
|
+
export declare function workspaceEnvVarsFor(platform: PlatformId): string[];
|
|
75
|
+
/**
|
|
76
|
+
* Issue #545 — return the union of workspace env vars from ALL platforms
|
|
77
|
+
* EXCEPT the given one. Consumed by Pi's bridge env scrub (strip foreign
|
|
78
|
+
* workspace vars from spawned MCP child) and by the matrix regression test.
|
|
35
79
|
*/
|
|
36
|
-
export declare
|
|
80
|
+
export declare function foreignWorkspaceEnv(platform: PlatformId): Set<string>;
|
|
37
81
|
/**
|
|
38
82
|
* Sync map from platform identifier → home-relative path segments where that
|
|
39
83
|
* platform stores its config. Mirrors the `super([...])` argument passed by
|
package/build/adapters/detect.js
CHANGED
|
@@ -59,10 +59,15 @@ export function __seedClaudeCodePluginCacheMissForTests() {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* High-confidence env vars per platform, checked in priority order.
|
|
62
|
-
* Single source of truth — consumed by detectPlatform() below
|
|
63
|
-
*
|
|
62
|
+
* Single source of truth — consumed by detectPlatform() below, by
|
|
63
|
+
* `resolveProjectDir({ strictPlatform })` for cascade construction, and by
|
|
64
|
+
* Pi's bridge env scrub. Tests also iterate this map to clear platform-
|
|
65
|
+
* related env vars deterministically.
|
|
66
|
+
*
|
|
67
|
+
* The map shape is `Map<PlatformId, ReadonlyArray<PlatformEnvEntry>>`. Use
|
|
68
|
+
* `getEnvVarNames(p)` to get just the names (legacy `string[]` shape).
|
|
64
69
|
*/
|
|
65
|
-
|
|
70
|
+
const _PLATFORM_ENV_VARS_RAW = [
|
|
66
71
|
// Order matters: forks listed BEFORE the fork's parent so collision
|
|
67
72
|
// detection works. Every entry verified against platform's own runtime
|
|
68
73
|
// source code (PR #376 follow-up: full audit, May 2026 — see git blame).
|
|
@@ -75,49 +80,84 @@ export const PLATFORM_ENV_VARS = [
|
|
|
75
80
|
// are the disambiguators for issue #539 (Claude Code running inside a
|
|
76
81
|
// VS Code integrated terminal that has VSCODE_PID set). They MUST be
|
|
77
82
|
// checked here so detect resolves to claude-code BEFORE falling through
|
|
78
|
-
// to vscode-copilot
|
|
83
|
+
// to vscode-copilot below.
|
|
79
84
|
["claude-code", [
|
|
80
|
-
"CLAUDE_CODE_ENTRYPOINT",
|
|
81
|
-
"CLAUDE_PLUGIN_ROOT",
|
|
82
|
-
"CLAUDE_PROJECT_DIR",
|
|
83
|
-
"CLAUDE_SESSION_ID",
|
|
85
|
+
{ name: "CLAUDE_CODE_ENTRYPOINT", role: "identification" },
|
|
86
|
+
{ name: "CLAUDE_PLUGIN_ROOT", role: "identification" },
|
|
87
|
+
{ name: "CLAUDE_PROJECT_DIR", role: "workspace" },
|
|
88
|
+
{ name: "CLAUDE_SESSION_ID", role: "identification" },
|
|
84
89
|
]],
|
|
85
90
|
// antigravity (Electron/VSCode fork) — google-gemini/gemini-cli
|
|
86
91
|
// packages/core/src/ide/detect-ide.ts checks ANTIGRAVITY_CLI_ALIAS as the
|
|
87
92
|
// canonical Antigravity marker. Listed before vscode-copilot.
|
|
88
|
-
["antigravity", [
|
|
93
|
+
["antigravity", [
|
|
94
|
+
{ name: "ANTIGRAVITY_CLI_ALIAS", role: "identification" },
|
|
95
|
+
]],
|
|
89
96
|
// cursor (VSCode fork) — listed before vscode-copilot. CURSOR_TRACE_ID has
|
|
90
97
|
// 800+ hits in major OSS detection libs (Vercel Next.js, Bun, Google
|
|
91
|
-
// gemini-cli, Nx, CrewAI).
|
|
92
|
-
|
|
98
|
+
// gemini-cli, Nx, CrewAI). CURSOR_CWD is the documented workspace var
|
|
99
|
+
// (issue #521) — listed first so workspace cascade picks it up.
|
|
100
|
+
["cursor", [
|
|
101
|
+
{ name: "CURSOR_CWD", role: "workspace" },
|
|
102
|
+
{ name: "CURSOR_TRACE_ID", role: "identification" },
|
|
103
|
+
{ name: "CURSOR_CLI", role: "identification" },
|
|
104
|
+
]],
|
|
93
105
|
// kilo (OpenCode fork) — Kilo-Org/kilocode packages/opencode/src/index.ts:138 + 139
|
|
94
|
-
// sets `process.env.KILO = 1` + `process.env.KILO_PID = String(process.pid)`.
|
|
95
|
-
["kilo", [
|
|
106
|
+
// sets `process.env.KILO = 1` + `process.env.KILO_PID = String(process.pid)`.
|
|
107
|
+
["kilo", [
|
|
108
|
+
{ name: "KILO", role: "identification" },
|
|
109
|
+
{ name: "KILO_PID", role: "identification" },
|
|
110
|
+
]],
|
|
96
111
|
// opencode — sst/opencode packages/opencode/src/index.ts:108-109 sets
|
|
97
112
|
// OPENCODE=1 + OPENCODE_PID=<pid> on every CLI invocation.
|
|
98
|
-
|
|
113
|
+
// OPENCODE_PROJECT_DIR is the documented workspace var (consumed by the
|
|
114
|
+
// legacy resolver cascade) — listed first so the workspace cascade picks
|
|
115
|
+
// it up under strict mode.
|
|
116
|
+
["opencode", [
|
|
117
|
+
{ name: "OPENCODE_PROJECT_DIR", role: "workspace" },
|
|
118
|
+
{ name: "OPENCODE", role: "identification" },
|
|
119
|
+
{ name: "OPENCODE_PID", role: "identification" },
|
|
120
|
+
]],
|
|
99
121
|
// zed — zed-industries/zed crates/terminal/src/terminal.rs sets ZED_TERM=true
|
|
100
122
|
// in `insert_zed_terminal_env()`. Google's gemini-cli uses ZED_SESSION_ID.
|
|
101
|
-
["zed", [
|
|
123
|
+
["zed", [
|
|
124
|
+
{ name: "ZED_SESSION_ID", role: "identification" },
|
|
125
|
+
{ name: "ZED_TERM", role: "identification" },
|
|
126
|
+
]],
|
|
102
127
|
// codex — openai/codex codex-rs/core/src/exec_env.rs sets CODEX_THREAD_ID
|
|
103
128
|
// per exec; unified_exec/process_manager.rs sets CODEX_CI in CI mode.
|
|
104
|
-
["codex", [
|
|
129
|
+
["codex", [
|
|
130
|
+
{ name: "CODEX_THREAD_ID", role: "identification" },
|
|
131
|
+
{ name: "CODEX_CI", role: "identification" },
|
|
132
|
+
]],
|
|
105
133
|
// gemini-cli — GEMINI_PROJECT_DIR per google-gemini/gemini-cli
|
|
106
134
|
// docs/hooks/index.md; GEMINI_CLI is the MCP-server sentinel.
|
|
107
|
-
["gemini-cli", [
|
|
135
|
+
["gemini-cli", [
|
|
136
|
+
{ name: "GEMINI_PROJECT_DIR", role: "workspace" },
|
|
137
|
+
{ name: "GEMINI_CLI", role: "identification" },
|
|
138
|
+
]],
|
|
108
139
|
// vscode-copilot — VSCODE_PID + VSCODE_CWD set by microsoft/vscode bootstrap.
|
|
109
140
|
// Listed AFTER cursor and antigravity since they inherit these vars as forks.
|
|
110
|
-
["vscode-copilot", [
|
|
141
|
+
["vscode-copilot", [
|
|
142
|
+
{ name: "VSCODE_CWD", role: "workspace" },
|
|
143
|
+
{ name: "VSCODE_PID", role: "identification" },
|
|
144
|
+
]],
|
|
111
145
|
// jetbrains-copilot — IDEA_INITIAL_DIRECTORY set by JetBrains launcher.
|
|
112
146
|
// (IDEA_HOME and JETBRAINS_CLIENT_ID removed — no source-line evidence.)
|
|
113
|
-
["jetbrains-copilot", [
|
|
147
|
+
["jetbrains-copilot", [
|
|
148
|
+
{ name: "IDEA_INITIAL_DIRECTORY", role: "workspace" },
|
|
149
|
+
]],
|
|
114
150
|
// qwen-code — QWEN_PROJECT_DIR per QwenLM/qwen-code docs/users/features/hooks.md.
|
|
115
151
|
// (QWEN_SESSION_ID removed — 0 hits in qwen-code repository.)
|
|
116
|
-
["qwen-code", [
|
|
152
|
+
["qwen-code", [
|
|
153
|
+
{ name: "QWEN_PROJECT_DIR", role: "workspace" },
|
|
154
|
+
]],
|
|
117
155
|
// omp (can1357/oh-my-pi). PI_CODING_AGENT_DIR is the upstream
|
|
118
156
|
// agent-dir override per `packages/utils/src/dirs.ts:193`. Listed
|
|
119
157
|
// BEFORE pi so OMP is not misclassified as Pi when both are installed.
|
|
120
|
-
["omp", [
|
|
158
|
+
["omp", [
|
|
159
|
+
{ name: "PI_CODING_AGENT_DIR", role: "workspace" },
|
|
160
|
+
]],
|
|
121
161
|
// pi — Issue #542 marker correction. PI_PROJECT_DIR is a consumer-set
|
|
122
162
|
// var (read by src/adapters/pi/extension.ts) but is NOT auto-set by
|
|
123
163
|
// the Pi runtime — verified against
|
|
@@ -126,11 +166,62 @@ export const PLATFORM_ENV_VARS = [
|
|
|
126
166
|
// PI_CONFIG_DIR (config dir override), PI_SESSION_FILE (active session
|
|
127
167
|
// path), and PI_COMPILED (binary build marker). PI_CODING_AGENT_DIR is
|
|
128
168
|
// owned by OMP above; keep it there.
|
|
129
|
-
|
|
169
|
+
//
|
|
170
|
+
// Issue #545 — PI_WORKSPACE_DIR / PI_PROJECT_DIR are workspace vars set
|
|
171
|
+
// by Pi's bridge so the resolver picks them up under strict mode.
|
|
172
|
+
// PI_WORKSPACE_DIR comes first (extension-set, freshest) before
|
|
173
|
+
// PI_PROJECT_DIR (user override) per registry-author cascade order.
|
|
174
|
+
["pi", [
|
|
175
|
+
// Issue #545 — workspace vars set by Pi's bridge so resolveProjectDir
|
|
176
|
+
// under strict mode picks them up. detect=false because PI_*_DIR are
|
|
177
|
+
// consumer-set and must NOT misclassify a non-Pi host as Pi (#542).
|
|
178
|
+
{ name: "PI_WORKSPACE_DIR", role: "workspace", detect: false },
|
|
179
|
+
{ name: "PI_PROJECT_DIR", role: "workspace", detect: false },
|
|
180
|
+
{ name: "PI_CONFIG_DIR", role: "identification" },
|
|
181
|
+
{ name: "PI_SESSION_FILE", role: "identification" },
|
|
182
|
+
{ name: "PI_COMPILED", role: "identification" },
|
|
183
|
+
]],
|
|
130
184
|
// openclaw — removed (runtime never sets OPENCLAW_HOME or OPENCLAW_CLI;
|
|
131
185
|
// detection falls through to ~/.openclaw/ config-dir tier below).
|
|
132
186
|
// kiro — not listed (no auto-set process env vars; ~/.kiro/ config-dir tier).
|
|
133
187
|
];
|
|
188
|
+
export const PLATFORM_ENV_VARS = new Map(_PLATFORM_ENV_VARS_RAW);
|
|
189
|
+
/**
|
|
190
|
+
* Backwards-compat shim: legacy `string[]` shape used by detection logic and
|
|
191
|
+
* by tests that iterate the registry to clear env vars. Always returns the
|
|
192
|
+
* names in registry order.
|
|
193
|
+
*/
|
|
194
|
+
export function getEnvVarNames(platform) {
|
|
195
|
+
return (PLATFORM_ENV_VARS.get(platform) ?? []).map((e) => e.name);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Issue #545 — return only role=workspace env var names for a platform, in
|
|
199
|
+
* registry order. Empty array for adapters with no workspace var (e.g.
|
|
200
|
+
* codex, kilo, zed, antigravity, openclaw, kiro). Consumed by
|
|
201
|
+
* `resolveProjectDir({ strictPlatform })` to build the cascade.
|
|
202
|
+
*/
|
|
203
|
+
export function workspaceEnvVarsFor(platform) {
|
|
204
|
+
return (PLATFORM_ENV_VARS.get(platform) ?? [])
|
|
205
|
+
.filter((e) => e.role === "workspace")
|
|
206
|
+
.map((e) => e.name);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Issue #545 — return the union of workspace env vars from ALL platforms
|
|
210
|
+
* EXCEPT the given one. Consumed by Pi's bridge env scrub (strip foreign
|
|
211
|
+
* workspace vars from spawned MCP child) and by the matrix regression test.
|
|
212
|
+
*/
|
|
213
|
+
export function foreignWorkspaceEnv(platform) {
|
|
214
|
+
const ban = new Set();
|
|
215
|
+
for (const [p, vars] of PLATFORM_ENV_VARS) {
|
|
216
|
+
if (p === platform)
|
|
217
|
+
continue;
|
|
218
|
+
for (const v of vars) {
|
|
219
|
+
if (v.role === "workspace")
|
|
220
|
+
ban.add(v.name);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return ban;
|
|
224
|
+
}
|
|
134
225
|
/**
|
|
135
226
|
* Sync map from platform identifier → home-relative path segments where that
|
|
136
227
|
* platform stores its config. Mirrors the `super([...])` argument passed by
|
|
@@ -204,7 +295,7 @@ export function detectPlatform(clientInfo) {
|
|
|
204
295
|
}
|
|
205
296
|
// ── High confidence: environment variables ─────────────
|
|
206
297
|
for (const [platform, vars] of PLATFORM_ENV_VARS) {
|
|
207
|
-
if (vars.some((v) => process.env[v])) {
|
|
298
|
+
if (vars.some((v) => v.detect !== false && process.env[v.name])) {
|
|
208
299
|
// Issue #539 belt-and-suspenders: VSCODE_PID/VSCODE_CWD are exported
|
|
209
300
|
// by VS Code into EVERY child process — including a Claude Code CLI
|
|
210
301
|
// launched from the integrated terminal. If env vars alone want to
|
|
@@ -224,7 +315,7 @@ export function detectPlatform(clientInfo) {
|
|
|
224
315
|
return {
|
|
225
316
|
platform,
|
|
226
317
|
confidence: "high",
|
|
227
|
-
reason: `${vars.join(" or ")} env var set`,
|
|
318
|
+
reason: `${vars.filter((v) => v.detect !== false).map((v) => v.name).join(" or ")} env var set`,
|
|
228
319
|
};
|
|
229
320
|
}
|
|
230
321
|
}
|
|
@@ -102,7 +102,7 @@ function getPlatform() {
|
|
|
102
102
|
for (const [platform, vars] of PLATFORM_ENV_VARS) {
|
|
103
103
|
if (platform !== "kilo" && platform !== "opencode")
|
|
104
104
|
continue;
|
|
105
|
-
if (vars.some((v) => process.env[v])) {
|
|
105
|
+
if (vars.some((v) => process.env[v.name])) {
|
|
106
106
|
return platform;
|
|
107
107
|
}
|
|
108
108
|
}
|