context-mode 1.0.131 → 1.0.133
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 +26 -15
- package/build/cli.js +32 -0
- package/build/lifecycle.d.ts +51 -2
- package/build/lifecycle.js +67 -3
- package/build/server.js +118 -16
- package/build/session/analytics.d.ts +38 -0
- package/build/session/analytics.js +58 -1
- package/build/session/extract.d.ts +7 -0
- package/build/session/extract.js +22 -6
- package/build/store.d.ts +17 -2
- package/build/store.js +17 -13
- package/build/util/sibling-mcp.d.ts +40 -0
- package/build/util/sibling-mcp.js +116 -11
- package/cli.bundle.mjs +174 -165
- package/configs/jetbrains-copilot/mcp.json +1 -2
- package/configs/vscode-copilot/mcp.json +1 -2
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/session-loaders.mjs +15 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -2
- package/scripts/heal-better-sqlite3.mjs +99 -2
- package/scripts/postinstall.mjs +58 -0
- package/server.bundle.mjs +106 -104
- package/skills/context-mode/SKILL.md +1 -0
- package/skills/context-mode/references/anti-patterns.md +26 -0
|
@@ -26,19 +26,70 @@
|
|
|
26
26
|
* cross-platform without spawning real processes.
|
|
27
27
|
*/
|
|
28
28
|
import { execFileSync } from "node:child_process";
|
|
29
|
-
// Match
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
29
|
+
// Match every shape an installed context-mode MCP server can take in argv:
|
|
30
|
+
//
|
|
31
|
+
// 1. `~/.claude/plugins/cache/context-mode/context-mode/<v>/start.mjs`
|
|
32
|
+
// and `~/.claude/plugins/marketplaces/context-mode/start.mjs` — Claude
|
|
33
|
+
// Code plugin shapes (original #559 case).
|
|
34
|
+
// 2. `<prefix>/node_modules/context-mode/start.mjs` or `.../server.bundle.mjs`
|
|
35
|
+
// — npm-global, marketplace, manual installs.
|
|
36
|
+
// 3. `<prefix>/bin/context-mode` — npm-global bin shim. This is the argv
|
|
37
|
+
// OpenCode + KiloCode see when they spawn `mcp.command = ["context-mode"]`.
|
|
38
|
+
// Without this entry, sibling discovery missed the 26-child / 1.6 GB
|
|
39
|
+
// RSS accumulation reported in #565.
|
|
40
|
+
// 4. `bun .../context-mode/server.bundle.mjs` — Pi/Bun hosts.
|
|
41
|
+
//
|
|
42
|
+
// All four can be alive concurrently — VERDICT R1 dump confirmed multi-version
|
|
43
|
+
// coexistence on real macOS, and #565 confirmed concurrent OpenCode children
|
|
44
|
+
// alongside Claude Code children on Linux.
|
|
45
|
+
const POSIX_PGREP_PATTERN = "(node|bun).*(plugins/(cache|marketplaces)/.*context-mode.*start\\.mjs" +
|
|
46
|
+
"|context-mode/start\\.mjs" +
|
|
47
|
+
"|context-mode/server\\.bundle\\.mjs" +
|
|
48
|
+
"|bin/context-mode($|[^a-zA-Z0-9_-]))";
|
|
34
49
|
// Windows: PowerShell + Get-CimInstance (wmic deprecated since Win11 22H2).
|
|
35
|
-
// Filter
|
|
36
|
-
//
|
|
37
|
-
// uses regex-ish escaping at the JS layer.
|
|
50
|
+
// Filter includes both node.exe and bun.exe (Pi/Bun hosts). CommandLine
|
|
51
|
+
// matching covers the same four install shapes as POSIX_PGREP_PATTERN.
|
|
38
52
|
const WIN_PS_SCRIPT = "Get-CimInstance Win32_Process " +
|
|
39
|
-
"-Filter \"Name='node.exe'\" | " +
|
|
40
|
-
"Where-Object { $_.CommandLine -match
|
|
53
|
+
"-Filter \"Name='node.exe' OR Name='bun.exe'\" | " +
|
|
54
|
+
"Where-Object { $_.CommandLine -match " +
|
|
55
|
+
"'plugins[\\\\/](cache|marketplaces)[\\\\/].*context-mode.*start\\.mjs" +
|
|
56
|
+
"|context-mode[\\\\/]start\\.mjs" +
|
|
57
|
+
"|context-mode[\\\\/]server\\.bundle\\.mjs" +
|
|
58
|
+
"|bin[\\\\/]context-mode($|[^a-zA-Z0-9_-])' } | " +
|
|
41
59
|
"Select-Object -ExpandProperty ProcessId";
|
|
60
|
+
/** POSIX ppid probe — empty / NaN on failure. */
|
|
61
|
+
function readPpidPosix(pid) {
|
|
62
|
+
try {
|
|
63
|
+
const out = execFileSync("ps", ["-o", "ppid=", "-p", String(pid)], {
|
|
64
|
+
encoding: "utf-8",
|
|
65
|
+
timeout: 2000,
|
|
66
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
67
|
+
}).trim();
|
|
68
|
+
const n = Number.parseInt(out, 10);
|
|
69
|
+
return Number.isFinite(n) ? n : NaN;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return NaN;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Windows ppid probe — empty / NaN on failure. */
|
|
76
|
+
function readPpidWin32(pid) {
|
|
77
|
+
try {
|
|
78
|
+
const out = execFileSync("powershell", [
|
|
79
|
+
"-NoProfile",
|
|
80
|
+
"-Command",
|
|
81
|
+
`(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").ParentProcessId`,
|
|
82
|
+
], { encoding: "utf-8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
83
|
+
const n = Number.parseInt(out, 10);
|
|
84
|
+
return Number.isFinite(n) ? n : NaN;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return NaN;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function defaultReadPpid(pid) {
|
|
91
|
+
return process.platform === "win32" ? readPpidWin32(pid) : readPpidPosix(pid);
|
|
92
|
+
}
|
|
42
93
|
const defaultRun = (cmd, args) => execFileSync(cmd, [...args], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
43
94
|
const defaultIsAlive = (pid) => {
|
|
44
95
|
try {
|
|
@@ -94,7 +145,18 @@ export function discoverSiblingMcpPids(opts) {
|
|
|
94
145
|
catch {
|
|
95
146
|
return [];
|
|
96
147
|
}
|
|
97
|
-
|
|
148
|
+
const candidates = parsePidList(stdout).filter((pid) => pid !== opts.ownPid && pid !== opts.ownPpid);
|
|
149
|
+
if (!opts.sameParentOnly)
|
|
150
|
+
return candidates;
|
|
151
|
+
// Startup-sweep mode (#565): only reap siblings sharing OUR ppid. This
|
|
152
|
+
// prevents an opencode-spawned MCP child from killing a Claude Code MCP
|
|
153
|
+
// child (or another concurrent opencode host's children) when both are
|
|
154
|
+
// present on the same machine.
|
|
155
|
+
const readPpid = opts.readPpid ?? defaultReadPpid;
|
|
156
|
+
return candidates.filter((pid) => {
|
|
157
|
+
const ppid = readPpid(pid);
|
|
158
|
+
return Number.isFinite(ppid) && ppid === opts.ownPpid;
|
|
159
|
+
});
|
|
98
160
|
}
|
|
99
161
|
/** Sleep helper — Promise-based for use inside the kill polling loop. */
|
|
100
162
|
function delay(ms) {
|
|
@@ -179,3 +241,46 @@ export async function killSiblingMcpServers(opts) {
|
|
|
179
241
|
totalKilled: terminatedBySigterm + terminatedBySigkill,
|
|
180
242
|
};
|
|
181
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Startup-time sibling sweep (#565).
|
|
246
|
+
*
|
|
247
|
+
* Discovers any context-mode MCP server pids that share OUR parent process
|
|
248
|
+
* (i.e. other MCP children of the same host like `opencode serve`) and
|
|
249
|
+
* terminates them. The intent is "exactly one MCP child per host" — when a
|
|
250
|
+
* new MCP client spawns inside an opencode host that already has 25 stale
|
|
251
|
+
* idle siblings, this sweep reclaims them at boot rather than waiting for
|
|
252
|
+
* the idle timeout to fire on each one independently.
|
|
253
|
+
*
|
|
254
|
+
* Gated by env (default-on but easy to disable):
|
|
255
|
+
*
|
|
256
|
+
* CONTEXT_MODE_STARTUP_SWEEP=0 → disabled
|
|
257
|
+
* CONTEXT_MODE_STARTUP_SWEEP=1 → enabled (default)
|
|
258
|
+
*
|
|
259
|
+
* Safety:
|
|
260
|
+
* - `sameParentOnly: true` — never touches MCP children of a different host.
|
|
261
|
+
* - Best-effort throughout: failures never block server startup.
|
|
262
|
+
* - Composes with the idle-timeout path: if a sibling is actively in use
|
|
263
|
+
* by another session, the parent process will simply spawn a new MCP
|
|
264
|
+
* child on its next request. The cost is one cold-start (~1–3 s) for
|
|
265
|
+
* that session, which is identical to opencode's existing behaviour
|
|
266
|
+
* of spawning a fresh MCP child per session anyway.
|
|
267
|
+
*/
|
|
268
|
+
export async function startupSiblingSweep(env = process.env) {
|
|
269
|
+
const empty = { terminatedBySigterm: 0, terminatedBySigkill: 0, totalKilled: 0 };
|
|
270
|
+
const raw = env.CONTEXT_MODE_STARTUP_SWEEP;
|
|
271
|
+
if (raw === "0" || raw === "false")
|
|
272
|
+
return empty;
|
|
273
|
+
try {
|
|
274
|
+
const pids = discoverSiblingMcpPids({
|
|
275
|
+
ownPid: process.pid,
|
|
276
|
+
ownPpid: process.ppid,
|
|
277
|
+
sameParentOnly: true,
|
|
278
|
+
});
|
|
279
|
+
if (pids.length === 0)
|
|
280
|
+
return empty;
|
|
281
|
+
return await killSiblingMcpServers({ pids });
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return empty;
|
|
285
|
+
}
|
|
286
|
+
}
|