context-mode 1.0.134 → 1.0.135
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/build/adapters/detect.d.ts +3 -1
- package/build/adapters/detect.js +7 -2
- package/build/adapters/pi/mcp-bridge.d.ts +8 -0
- package/build/adapters/pi/mcp-bridge.js +32 -0
- package/build/cli.js +17 -0
- package/build/runtime.js +8 -5
- package/build/session/analytics.d.ts +0 -13
- package/build/session/analytics.js +50 -1
- package/build/util/claude-config.d.ts +12 -6
- package/build/util/claude-config.js +16 -23
- package/cli.bundle.mjs +135 -133
- package/hooks/codex/sessionstart.mjs +23 -1
- package/hooks/core/platform-detect.mjs +1 -1
- package/hooks/normalize-hooks.mjs +5 -2
- package/hooks/security.bundle.mjs +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/heal-installed-plugins.mjs +67 -0
- package/server.bundle.mjs +99 -99
- package/start.mjs +73 -11
|
@@ -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.135"
|
|
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.135",
|
|
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.135",
|
|
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.135",
|
|
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.135",
|
|
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",
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
|
|
12
12
|
* - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
|
|
13
13
|
* - KiloCode: KILO, KILO_PID | ~/.config/kilo/
|
|
14
|
-
* - OpenCode:
|
|
14
|
+
* - OpenCode: OPENCODE_PROJECT_DIR, OPENCODE_CLIENT,
|
|
15
|
+
* OPENCODE_TERMINAL, OPENCODE, OPENCODE_PID |
|
|
16
|
+
* ~/.config/opencode/
|
|
15
17
|
* - OpenClaw: OPENCLAW_HOME, OPENCLAW_CLI | ~/.openclaw/
|
|
16
18
|
* - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
|
|
17
19
|
* - Cursor: CURSOR_TRACE_ID (MCP), CURSOR_CLI (terminal) | ~/.cursor/
|
package/build/adapters/detect.js
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
|
|
12
12
|
* - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
|
|
13
13
|
* - KiloCode: KILO, KILO_PID | ~/.config/kilo/
|
|
14
|
-
* - OpenCode:
|
|
14
|
+
* - OpenCode: OPENCODE_PROJECT_DIR, OPENCODE_CLIENT,
|
|
15
|
+
* OPENCODE_TERMINAL, OPENCODE, OPENCODE_PID |
|
|
16
|
+
* ~/.config/opencode/
|
|
15
17
|
* - OpenClaw: OPENCLAW_HOME, OPENCLAW_CLI | ~/.openclaw/
|
|
16
18
|
* - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
|
|
17
19
|
* - Cursor: CURSOR_TRACE_ID (MCP), CURSOR_CLI (terminal) | ~/.cursor/
|
|
@@ -109,12 +111,15 @@ const _PLATFORM_ENV_VARS_RAW = [
|
|
|
109
111
|
{ name: "KILO_PID", role: "identification" },
|
|
110
112
|
]],
|
|
111
113
|
// opencode — sst/opencode packages/opencode/src/index.ts:108-109 sets
|
|
112
|
-
// OPENCODE=1 + OPENCODE_PID=<pid> on
|
|
114
|
+
// OPENCODE=1 + OPENCODE_PID=<pid> on CLI invocations. OpenCode desktop
|
|
115
|
+
// shells also expose OPENCODE_CLIENT=desktop and OPENCODE_TERMINAL=1.
|
|
113
116
|
// OPENCODE_PROJECT_DIR is the documented workspace var (consumed by the
|
|
114
117
|
// legacy resolver cascade) — listed first so the workspace cascade picks
|
|
115
118
|
// it up under strict mode.
|
|
116
119
|
["opencode", [
|
|
117
120
|
{ name: "OPENCODE_PROJECT_DIR", role: "workspace" },
|
|
121
|
+
{ name: "OPENCODE_CLIENT", role: "identification" },
|
|
122
|
+
{ name: "OPENCODE_TERMINAL", role: "identification" },
|
|
118
123
|
{ name: "OPENCODE", role: "identification" },
|
|
119
124
|
{ name: "OPENCODE_PID", role: "identification" },
|
|
120
125
|
]],
|
|
@@ -85,6 +85,14 @@ export declare class MCPStdioClient {
|
|
|
85
85
|
initialize(): Promise<void>;
|
|
86
86
|
listTools(): Promise<MCPTool[]>;
|
|
87
87
|
callTool(name: string, args: unknown): Promise<MCPCallResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Respawn the MCP child after an exit (clean idle shutdown or crash).
|
|
90
|
+
* Resets state so a fresh `start()` + `initialize()` cycle runs, then
|
|
91
|
+
* the caller's pending request flows through the new child.
|
|
92
|
+
*
|
|
93
|
+
* Internal — exposed only via the public `callTool()` happy path.
|
|
94
|
+
*/
|
|
95
|
+
private respawn;
|
|
88
96
|
shutdown(): void;
|
|
89
97
|
}
|
|
90
98
|
/**
|
|
@@ -284,8 +284,40 @@ export class MCPStdioClient {
|
|
|
284
284
|
return Array.isArray(result.tools) ? result.tools : [];
|
|
285
285
|
}
|
|
286
286
|
async callTool(name, args) {
|
|
287
|
+
// Respawn-on-idle-exit (#583). The MCP server gained an idle
|
|
288
|
+
// self-shutdown in 1.0.132 (#565/#568, src/lifecycle.ts). When the
|
|
289
|
+
// Pi-spawned child exits cleanly after the idle window, Pi keeps the
|
|
290
|
+
// tool handles registered, but the bridge client is `exited=true`
|
|
291
|
+
// and every subsequent request would reject with
|
|
292
|
+
// "MCP server has exited" — leaving Pi's ctx_* tools permanently
|
|
293
|
+
// broken until the user restarts Pi.
|
|
294
|
+
//
|
|
295
|
+
// The structural fix is here, not in lifecycle.ts: the bridge owns
|
|
296
|
+
// the child lifecycle, so it transparently respawns + re-initialises
|
|
297
|
+
// the server on the next call. Restores parity with adapters whose
|
|
298
|
+
// host MCP client respawns on EOF (Claude Code, Codex, etc.).
|
|
299
|
+
if (this.exited)
|
|
300
|
+
await this.respawn();
|
|
287
301
|
return this.request("tools/call", { name, arguments: args ?? {} }, DEFAULT_CALL_TIMEOUT_MS);
|
|
288
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Respawn the MCP child after an exit (clean idle shutdown or crash).
|
|
305
|
+
* Resets state so a fresh `start()` + `initialize()` cycle runs, then
|
|
306
|
+
* the caller's pending request flows through the new child.
|
|
307
|
+
*
|
|
308
|
+
* Internal — exposed only via the public `callTool()` happy path.
|
|
309
|
+
*/
|
|
310
|
+
async respawn() {
|
|
311
|
+
// Drop the dead child handle and clear stream buffer so leftover
|
|
312
|
+
// bytes from the previous incarnation don't get parsed as JSON-RPC
|
|
313
|
+
// for the new one. Pending map is already cleared by onExit().
|
|
314
|
+
this.child = null;
|
|
315
|
+
this.buffer = "";
|
|
316
|
+
this.exited = false;
|
|
317
|
+
this.initialized = false;
|
|
318
|
+
this.start();
|
|
319
|
+
await this.initialize();
|
|
320
|
+
}
|
|
289
321
|
shutdown() {
|
|
290
322
|
if (!this.child)
|
|
291
323
|
return;
|
package/build/cli.js
CHANGED
|
@@ -933,6 +933,22 @@ async function upgrade(opts) {
|
|
|
933
933
|
const message = err instanceof Error ? err.message : String(err);
|
|
934
934
|
throw new Error(`.mcp.json drift check failed: ${message}`);
|
|
935
935
|
}
|
|
936
|
+
// v1.0.X — Layer 7 heal: update user-level ~/.claude.json MCP server
|
|
937
|
+
// registrations that point to old context-mode version dirs.
|
|
938
|
+
// (anthropics/claude-code#59310 workaround — see heal-installed-plugins.mjs)
|
|
939
|
+
try {
|
|
940
|
+
// @ts-expect-error — JS module, no TS declarations
|
|
941
|
+
const { healClaudeJsonMcpArgs } = await import("../scripts/heal-installed-plugins.mjs");
|
|
942
|
+
const dotClaudeJson = resolve(homedir(), ".claude.json");
|
|
943
|
+
const pluginCacheParent = resolve(resolveClaudeConfigDir(), "plugins", "cache", "context-mode", "context-mode");
|
|
944
|
+
const result = healClaudeJsonMcpArgs({ dotClaudeJsonPath: dotClaudeJson, pluginCacheParent, newPluginRoot: pluginRoot });
|
|
945
|
+
if (result.healed && result.healed.length > 0) {
|
|
946
|
+
p.log.info(color.dim(" ~/.claude.json user MCP registrations updated → " + newVersion));
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
/* best effort — never block upgrade */
|
|
951
|
+
}
|
|
936
952
|
// v1.0.114 hotfix — marketplace post-pull assertion: clone (if
|
|
937
953
|
// present) MUST be on newVersion. Mert's case showed marketplace
|
|
938
954
|
// stuck at v1.0.89 — the sync block above swallowed that silently.
|
|
@@ -1151,6 +1167,7 @@ async function upgrade(opts) {
|
|
|
1151
1167
|
stdio: "inherit",
|
|
1152
1168
|
timeout: 30000,
|
|
1153
1169
|
cwd: pluginRoot,
|
|
1170
|
+
env: { ...process.env, CONTEXT_MODE_PLATFORM: detection.platform },
|
|
1154
1171
|
});
|
|
1155
1172
|
}
|
|
1156
1173
|
catch {
|
package/build/runtime.js
CHANGED
|
@@ -12,11 +12,14 @@ import { existsSync } from "node:fs";
|
|
|
12
12
|
* Match is case-insensitive; `.exe` extension tolerated for Windows binaries.
|
|
13
13
|
*/
|
|
14
14
|
const ALLOWED_SHELL_BASENAMES = /^(bash|sh|zsh|dash|pwsh|powershell|cmd)(\.exe)?$/i;
|
|
15
|
+
const BUN_BASENAME = /^bun(\.exe)?$/i;
|
|
16
|
+
function runtimeBasename(runtimePath) {
|
|
17
|
+
const segments = runtimePath.split(/[\\/]/);
|
|
18
|
+
return segments[segments.length - 1] ?? runtimePath;
|
|
19
|
+
}
|
|
15
20
|
export function isAllowlistedShell(shellPath) {
|
|
16
21
|
// Cross-OS basename: split on either separator, take the last segment.
|
|
17
|
-
|
|
18
|
-
const base = segments[segments.length - 1];
|
|
19
|
-
return ALLOWED_SHELL_BASENAMES.test(base);
|
|
22
|
+
return ALLOWED_SHELL_BASENAMES.test(runtimeBasename(shellPath));
|
|
20
23
|
}
|
|
21
24
|
const isWindows = process.platform === "win32";
|
|
22
25
|
function commandExists(cmd) {
|
|
@@ -305,14 +308,14 @@ export function getAvailableLanguages(runtimes) {
|
|
|
305
308
|
export function buildCommand(runtimes, language, filePath) {
|
|
306
309
|
switch (language) {
|
|
307
310
|
case "javascript":
|
|
308
|
-
return runtimes.javascript
|
|
311
|
+
return BUN_BASENAME.test(runtimeBasename(runtimes.javascript))
|
|
309
312
|
? [runtimes.javascript, "run", filePath]
|
|
310
313
|
: [runtimes.javascript, filePath];
|
|
311
314
|
case "typescript":
|
|
312
315
|
if (!runtimes.typescript) {
|
|
313
316
|
throw new Error("No TypeScript runtime available. Install one of: bun (recommended), tsx (npm i -g tsx), or ts-node.");
|
|
314
317
|
}
|
|
315
|
-
if (runtimes.typescript
|
|
318
|
+
if (BUN_BASENAME.test(runtimeBasename(runtimes.typescript)))
|
|
316
319
|
return [runtimes.typescript, "run", filePath];
|
|
317
320
|
if (runtimes.typescript === "tsx")
|
|
318
321
|
return ["tsx", filePath];
|
|
@@ -557,19 +557,6 @@ export declare const adapterLabels: Record<string, string>;
|
|
|
557
557
|
* information. Scale awareness comes from the unit jump between rows.
|
|
558
558
|
*/
|
|
559
559
|
export declare function kb(b: number): string;
|
|
560
|
-
/**
|
|
561
|
-
* Locale + IANA-timezone detection for the narrative renderer.
|
|
562
|
-
*
|
|
563
|
-
* Cascade (each level overrides the next):
|
|
564
|
-
* 1. CONTEXT_MODE_LOCALE / CONTEXT_MODE_TZ env overrides
|
|
565
|
-
* (used by tests + by users who want to pin output regardless of OS).
|
|
566
|
-
* 2. macOS `defaults read -g AppleLocale` → `en_TR` style → `en-TR`.
|
|
567
|
-
* 3. Linux `LANG` / `LC_TIME` env vars.
|
|
568
|
-
* 4. Fallback: `Intl.DateTimeFormat().resolvedOptions().locale`.
|
|
569
|
-
*
|
|
570
|
-
* Timezone always uses `Intl.DateTimeFormat().resolvedOptions().timeZone`
|
|
571
|
-
* — that one's always available and correct regardless of platform.
|
|
572
|
-
*/
|
|
573
560
|
export declare function detectLocaleAndTz(): {
|
|
574
561
|
locale: string;
|
|
575
562
|
tz: string;
|
|
@@ -1217,9 +1217,45 @@ function formatDuration(uptimeMin) {
|
|
|
1217
1217
|
* Timezone always uses `Intl.DateTimeFormat().resolvedOptions().timeZone`
|
|
1218
1218
|
* — that one's always available and correct regardless of platform.
|
|
1219
1219
|
*/
|
|
1220
|
+
/**
|
|
1221
|
+
* Validate that a locale string is a usable BCP 47 tag.
|
|
1222
|
+
*
|
|
1223
|
+
* Ubuntu GHA runners default to `LANG=C.UTF-8`. The extractor below strips
|
|
1224
|
+
* that to `"C"` — a valid POSIX locale identifier but NOT a BCP 47 tag.
|
|
1225
|
+
* On macOS / Node 20, `new Intl.DateTimeFormat("C", …)` throws RangeError
|
|
1226
|
+
* outright. CI run 25887250971 caught this via the v1.0.134 SLICE B test.
|
|
1227
|
+
*
|
|
1228
|
+
* Earlier fix attempt used a permissive `supportedLocalesOf || construction`
|
|
1229
|
+
* OR check — that was wrong: on Linux + Node 22.5, `new Intl.DateTimeFormat
|
|
1230
|
+
* ("POSIX")` does NOT throw, it silently falls back to the root locale and
|
|
1231
|
+
* still emits garbage at format time. CI run 25904838577 surfaced that —
|
|
1232
|
+
* "POSIX" round-tripped through the validator unchanged.
|
|
1233
|
+
*
|
|
1234
|
+
* Strict gate: `Intl.DateTimeFormat.supportedLocalesOf(tag)` returns `[]` for
|
|
1235
|
+
* any tag that doesn't map to a real language (regardless of whether
|
|
1236
|
+
* construction with that tag throws). That's the contract we want — "is this
|
|
1237
|
+
* a BCP 47 tag the host actually has data for". Construction is an explicit
|
|
1238
|
+
* sanity check; both must pass.
|
|
1239
|
+
*/
|
|
1240
|
+
function isUsableBcp47Locale(raw) {
|
|
1241
|
+
if (!raw)
|
|
1242
|
+
return false;
|
|
1243
|
+
try {
|
|
1244
|
+
if (Intl.DateTimeFormat.supportedLocalesOf(raw).length === 0)
|
|
1245
|
+
return false;
|
|
1246
|
+
// Belt: confirm construction doesn't throw on this host either.
|
|
1247
|
+
new Intl.DateTimeFormat(raw);
|
|
1248
|
+
return true;
|
|
1249
|
+
}
|
|
1250
|
+
catch {
|
|
1251
|
+
return false;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1220
1254
|
export function detectLocaleAndTz() {
|
|
1221
1255
|
const env = (process.env ?? {});
|
|
1222
1256
|
let locale = env.CONTEXT_MODE_LOCALE ?? "";
|
|
1257
|
+
if (locale && !isUsableBcp47Locale(locale))
|
|
1258
|
+
locale = "";
|
|
1223
1259
|
if (!locale) {
|
|
1224
1260
|
if (process.platform === "darwin") {
|
|
1225
1261
|
try {
|
|
@@ -1234,11 +1270,18 @@ export function detectLocaleAndTz() {
|
|
|
1234
1270
|
locale = out.replace(/_/g, "-");
|
|
1235
1271
|
}
|
|
1236
1272
|
catch { /* defaults missing or sandbox */ }
|
|
1273
|
+
if (locale && !isUsableBcp47Locale(locale))
|
|
1274
|
+
locale = "";
|
|
1237
1275
|
}
|
|
1238
1276
|
if (!locale && (env.LC_TIME || env.LANG)) {
|
|
1239
1277
|
const raw = (env.LC_TIME || env.LANG || "").split(".")[0];
|
|
1240
1278
|
if (raw)
|
|
1241
1279
|
locale = raw.replace(/_/g, "-");
|
|
1280
|
+
// POSIX locale identifiers (`C`, `POSIX`) survive the simple extraction
|
|
1281
|
+
// above but blow up `new Intl.DateTimeFormat(locale, ...)`. Drop and
|
|
1282
|
+
// fall through to the host-default branch below.
|
|
1283
|
+
if (locale && !isUsableBcp47Locale(locale))
|
|
1284
|
+
locale = "";
|
|
1242
1285
|
}
|
|
1243
1286
|
if (!locale) {
|
|
1244
1287
|
try {
|
|
@@ -1258,7 +1301,13 @@ export function detectLocaleAndTz() {
|
|
|
1258
1301
|
tz = "UTC";
|
|
1259
1302
|
}
|
|
1260
1303
|
}
|
|
1261
|
-
|
|
1304
|
+
// Final belt-and-suspenders: if the locale we settled on is somehow still
|
|
1305
|
+
// unusable (env mutation between detection and return, contributor adding
|
|
1306
|
+
// a new extraction path that skips the validator), fall back to en-US so
|
|
1307
|
+
// formatLocalDateTime / monthDay / weekdayCap never throw at render time.
|
|
1308
|
+
if (!isUsableBcp47Locale(locale))
|
|
1309
|
+
locale = "en-US";
|
|
1310
|
+
return { locale, tz: tz || "UTC" };
|
|
1262
1311
|
}
|
|
1263
1312
|
/**
|
|
1264
1313
|
* Format an absolute path as a human-friendly display string by
|
|
@@ -13,12 +13,18 @@ export declare function resolveClaudeGlobalSettingsPath(env?: NodeJS.ProcessEnv)
|
|
|
13
13
|
* adapter is non-claude — claude is already covered by entry 2).
|
|
14
14
|
* 2. The claude global settings.json (always — defense in depth).
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
16
|
+
* Static import of `../adapters/detect.js` is safe — detect.ts only imports
|
|
17
|
+
* `node:` builtins, `./types.js` (type-only), and `./client-map.js` (pure
|
|
18
|
+
* data). It does NOT import claude-config back, so no cycle.
|
|
19
|
+
*
|
|
20
|
+
* History: this used `createRequire(import.meta.url).resolve(...)` to lazy-
|
|
21
|
+
* load detect at call time. That pattern requires `require(esm)`, which is
|
|
22
|
+
* flag-gated on Node 22.x before 22.12 (`--experimental-require-module`).
|
|
23
|
+
* CI run 25877550371 on Node 22.5 silently failed every detect.* call —
|
|
24
|
+
* the catch block ate the error and every cross-adapter deny-policy test
|
|
25
|
+
* returned an empty policy list. Static import sidesteps the require(esm)
|
|
26
|
+
* gate entirely, so the same code works on every supported Node version
|
|
27
|
+
* (20.x, 22.5, 22.12+, 24+) without needing the experimental flag.
|
|
22
28
|
*
|
|
23
29
|
* The returned array is deduplicated and order-stable: adapter-specific path
|
|
24
30
|
* first (most specific), claude global second (fallback).
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { resolve } from "node:path";
|
|
25
25
|
import { homedir } from "node:os";
|
|
26
|
-
import {
|
|
26
|
+
import { detectPlatform, getSessionDirSegments } from "../adapters/detect.js";
|
|
27
27
|
export function resolveClaudeConfigDir(env = process.env) {
|
|
28
28
|
const envVal = env.CLAUDE_CONFIG_DIR;
|
|
29
29
|
if (envVal && envVal.trim() !== "") {
|
|
@@ -50,34 +50,27 @@ export function resolveClaudeGlobalSettingsPath(env = process.env) {
|
|
|
50
50
|
* adapter is non-claude — claude is already covered by entry 2).
|
|
51
51
|
* 2. The claude global settings.json (always — defense in depth).
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
53
|
+
* Static import of `../adapters/detect.js` is safe — detect.ts only imports
|
|
54
|
+
* `node:` builtins, `./types.js` (type-only), and `./client-map.js` (pure
|
|
55
|
+
* data). It does NOT import claude-config back, so no cycle.
|
|
56
|
+
*
|
|
57
|
+
* History: this used `createRequire(import.meta.url).resolve(...)` to lazy-
|
|
58
|
+
* load detect at call time. That pattern requires `require(esm)`, which is
|
|
59
|
+
* flag-gated on Node 22.x before 22.12 (`--experimental-require-module`).
|
|
60
|
+
* CI run 25877550371 on Node 22.5 silently failed every detect.* call —
|
|
61
|
+
* the catch block ate the error and every cross-adapter deny-policy test
|
|
62
|
+
* returned an empty policy list. Static import sidesteps the require(esm)
|
|
63
|
+
* gate entirely, so the same code works on every supported Node version
|
|
64
|
+
* (20.x, 22.5, 22.12+, 24+) without needing the experimental flag.
|
|
59
65
|
*
|
|
60
66
|
* The returned array is deduplicated and order-stable: adapter-specific path
|
|
61
67
|
* first (most specific), claude global second (fallback).
|
|
62
68
|
*/
|
|
63
69
|
export function resolveAdapterGlobalSettingsPaths(env = process.env) {
|
|
64
70
|
const paths = [];
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let detected = null;
|
|
69
|
-
let segmentsFor = null;
|
|
70
|
-
try {
|
|
71
|
-
const lazyRequire = createRequire(import.meta.url);
|
|
72
|
-
const detect = lazyRequire("../adapters/detect.js");
|
|
73
|
-
detected = detect.detectPlatform();
|
|
74
|
-
segmentsFor = detect.getSessionDirSegments;
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// If detection fails for any reason, fall back to claude-only behavior.
|
|
78
|
-
}
|
|
79
|
-
if (detected && segmentsFor && detected.platform !== "claude-code") {
|
|
80
|
-
const segments = segmentsFor(detected.platform);
|
|
71
|
+
const detected = detectPlatform();
|
|
72
|
+
if (detected.platform !== "claude-code") {
|
|
73
|
+
const segments = getSessionDirSegments(detected.platform);
|
|
81
74
|
if (segments && segments.length > 0) {
|
|
82
75
|
paths.push(resolve(homedir(), ...segments, "settings.json"));
|
|
83
76
|
}
|