context-mode 1.0.131 → 1.0.132
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 +92 -14
- 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 +161 -154
- 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 +96 -96
- package/skills/context-mode/SKILL.md +1 -0
- package/skills/context-mode/references/anti-patterns.md +26 -0
package/build/session/extract.js
CHANGED
|
@@ -678,12 +678,28 @@ function extractExternalRef(input) {
|
|
|
678
678
|
}
|
|
679
679
|
if (refs.size === 0)
|
|
680
680
|
return [];
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
681
|
+
// ctx_fetch_and_index returns a preamble like
|
|
682
|
+
// "Fetched and indexed **5 sections** (47.50KB) from: <label>"
|
|
683
|
+
// Parse the size to credit bytes_avoided on the event so per-session
|
|
684
|
+
// honest-savings stats reflect what was kept out of the context window.
|
|
685
|
+
// KB literal in the preamble is decimal (KB = 1024 bytes per the formatter).
|
|
686
|
+
let bytesAvoided;
|
|
687
|
+
const preambleMatch = safeString(input.tool_response).match(/Fetched and indexed[^\(]*\(([\d.]+)\s*KB\)/i);
|
|
688
|
+
if (preambleMatch) {
|
|
689
|
+
const kb = Number(preambleMatch[1]);
|
|
690
|
+
if (Number.isFinite(kb) && kb > 0) {
|
|
691
|
+
bytesAvoided = Math.round(kb * 1024);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const event = {
|
|
695
|
+
type: "external_ref",
|
|
696
|
+
category: "external-ref",
|
|
697
|
+
data: safeString(Array.from(refs).join(", ")),
|
|
698
|
+
priority: 3,
|
|
699
|
+
};
|
|
700
|
+
if (bytesAvoided !== undefined)
|
|
701
|
+
event.bytes_avoided = bytesAvoided;
|
|
702
|
+
return [event];
|
|
687
703
|
}
|
|
688
704
|
/**
|
|
689
705
|
* Category 8: env (worktree)
|
package/build/store.d.ts
CHANGED
|
@@ -41,13 +41,25 @@ export declare class ContentStore {
|
|
|
41
41
|
content?: string;
|
|
42
42
|
path?: string;
|
|
43
43
|
source?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Optional FK metadata recorded on each indexed chunk so per-session
|
|
46
|
+
* honest-savings stats can join chunks → session_events. When omitted,
|
|
47
|
+
* chunks fall back to empty-string columns (legacy behaviour).
|
|
48
|
+
*/
|
|
49
|
+
attribution?: {
|
|
50
|
+
sessionId?: string;
|
|
51
|
+
eventId?: string;
|
|
52
|
+
};
|
|
44
53
|
}): IndexResult;
|
|
45
54
|
/**
|
|
46
55
|
* Index plain-text output (logs, build output, test results) by splitting
|
|
47
56
|
* into fixed-size line groups. Unlike markdown indexing, this does not
|
|
48
57
|
* look for headings — it chunks by line count with overlap.
|
|
49
58
|
*/
|
|
50
|
-
indexPlainText(content: string, source: string, linesPerChunk?: number
|
|
59
|
+
indexPlainText(content: string, source: string, linesPerChunk?: number, attribution?: {
|
|
60
|
+
sessionId?: string;
|
|
61
|
+
eventId?: string;
|
|
62
|
+
}): IndexResult;
|
|
51
63
|
/**
|
|
52
64
|
* Index JSON content by walking the object tree and using key paths
|
|
53
65
|
* as chunk titles (analogous to heading hierarchy in markdown). Objects
|
|
@@ -55,7 +67,10 @@ export declare class ContentStore {
|
|
|
55
67
|
*
|
|
56
68
|
* Falls back to `indexPlainText` if the content is not valid JSON.
|
|
57
69
|
*/
|
|
58
|
-
indexJSON(content: string, source: string, maxChunkBytes?: number
|
|
70
|
+
indexJSON(content: string, source: string, maxChunkBytes?: number, attribution?: {
|
|
71
|
+
sessionId?: string;
|
|
72
|
+
eventId?: string;
|
|
73
|
+
}): IndexResult;
|
|
59
74
|
search(query: string, limit?: number, source?: string, mode?: "AND" | "OR", contentType?: "code" | "prose", sourceMatchMode?: SourceMatchMode): SearchResult[];
|
|
60
75
|
searchTrigram(query: string, limit?: number, source?: string, mode?: "AND" | "OR", contentType?: "code" | "prose", sourceMatchMode?: SourceMatchMode): SearchResult[];
|
|
61
76
|
fuzzyCorrect(query: string): string | null;
|
package/build/store.js
CHANGED
|
@@ -714,7 +714,7 @@ export class ContentStore {
|
|
|
714
714
|
}
|
|
715
715
|
// ── Index ──
|
|
716
716
|
index(options) {
|
|
717
|
-
const { content, path, source } = options;
|
|
717
|
+
const { content, path, source, attribution } = options;
|
|
718
718
|
// Treat empty string as "no content" so an empty `content` paired with a
|
|
719
719
|
// valid `path` falls back to reading the file. Some MCP clients
|
|
720
720
|
// materialize optional string fields as `""` and the previous
|
|
@@ -754,7 +754,7 @@ export class ContentStore {
|
|
|
754
754
|
// Stale detection: store file_path + SHA-256 for file-backed sources
|
|
755
755
|
const filePath = path ?? undefined;
|
|
756
756
|
const contentHash = filePath ? createHash("sha256").update(text).digest("hex") : undefined;
|
|
757
|
-
return withRetry(() => this.#insertChunks(chunks, label, text, filePath, contentHash));
|
|
757
|
+
return withRetry(() => this.#insertChunks(chunks, label, text, filePath, contentHash, attribution));
|
|
758
758
|
}
|
|
759
759
|
// ── Index Plain Text ──
|
|
760
760
|
/**
|
|
@@ -762,12 +762,12 @@ export class ContentStore {
|
|
|
762
762
|
* into fixed-size line groups. Unlike markdown indexing, this does not
|
|
763
763
|
* look for headings — it chunks by line count with overlap.
|
|
764
764
|
*/
|
|
765
|
-
indexPlainText(content, source, linesPerChunk = 20) {
|
|
765
|
+
indexPlainText(content, source, linesPerChunk = 20, attribution) {
|
|
766
766
|
if (!content || content.trim().length === 0) {
|
|
767
|
-
return this.#insertChunks([], source, "");
|
|
767
|
+
return this.#insertChunks([], source, "", undefined, undefined, attribution);
|
|
768
768
|
}
|
|
769
769
|
const chunks = this.#chunkPlainText(content, linesPerChunk);
|
|
770
|
-
return withRetry(() => this.#insertChunks(chunks.map((c) => ({ ...c, hasCode: false })), source, content));
|
|
770
|
+
return withRetry(() => this.#insertChunks(chunks.map((c) => ({ ...c, hasCode: false })), source, content, undefined, undefined, attribution));
|
|
771
771
|
}
|
|
772
772
|
// ── Index JSON ──
|
|
773
773
|
/**
|
|
@@ -777,23 +777,23 @@ export class ContentStore {
|
|
|
777
777
|
*
|
|
778
778
|
* Falls back to `indexPlainText` if the content is not valid JSON.
|
|
779
779
|
*/
|
|
780
|
-
indexJSON(content, source, maxChunkBytes = MAX_CHUNK_BYTES) {
|
|
780
|
+
indexJSON(content, source, maxChunkBytes = MAX_CHUNK_BYTES, attribution) {
|
|
781
781
|
if (!content || content.trim().length === 0) {
|
|
782
|
-
return this.indexPlainText("", source);
|
|
782
|
+
return this.indexPlainText("", source, undefined, attribution);
|
|
783
783
|
}
|
|
784
784
|
let parsed;
|
|
785
785
|
try {
|
|
786
786
|
parsed = JSON.parse(content);
|
|
787
787
|
}
|
|
788
788
|
catch {
|
|
789
|
-
return this.indexPlainText(content, source);
|
|
789
|
+
return this.indexPlainText(content, source, undefined, attribution);
|
|
790
790
|
}
|
|
791
791
|
const chunks = [];
|
|
792
792
|
this.#walkJSON(parsed, [], chunks, maxChunkBytes);
|
|
793
793
|
if (chunks.length === 0) {
|
|
794
|
-
return this.indexPlainText(content, source);
|
|
794
|
+
return this.indexPlainText(content, source, undefined, attribution);
|
|
795
795
|
}
|
|
796
|
-
return withRetry(() => this.#insertChunks(chunks, source, content));
|
|
796
|
+
return withRetry(() => this.#insertChunks(chunks, source, content, undefined, undefined, attribution));
|
|
797
797
|
}
|
|
798
798
|
// ── Shared DB Insertion ──
|
|
799
799
|
/**
|
|
@@ -801,8 +801,12 @@ export class ContentStore {
|
|
|
801
801
|
* into both FTS5 tables within a transaction and extracts vocabulary.
|
|
802
802
|
* Uses cached prepared statements from #prepareStatements().
|
|
803
803
|
*/
|
|
804
|
-
#insertChunks(chunks, label, text, filePath, contentHash) {
|
|
804
|
+
#insertChunks(chunks, label, text, filePath, contentHash, attribution) {
|
|
805
805
|
const codeChunks = chunks.filter((c) => c.hasCode).length;
|
|
806
|
+
// FK columns on chunks. Empty-string fallback preserves the FTS5-friendly
|
|
807
|
+
// "not-null but unattributed" sentinel used by legacy rows.
|
|
808
|
+
const sessionIdCol = attribution?.sessionId ?? "";
|
|
809
|
+
const eventIdCol = attribution?.eventId ?? "";
|
|
806
810
|
// Atomic dedup + insert: delete previous source with same label,
|
|
807
811
|
// then insert new content — all within a single transaction.
|
|
808
812
|
// Prevents stale results in iterative workflows. (See: GitHub issue #67)
|
|
@@ -819,8 +823,8 @@ export class ContentStore {
|
|
|
819
823
|
const now = new Date().toISOString();
|
|
820
824
|
for (const chunk of chunks) {
|
|
821
825
|
const ct = chunk.hasCode ? "code" : "prose";
|
|
822
|
-
this.#stmtInsertChunk.run(chunk.title, chunk.content, sourceId, ct, null,
|
|
823
|
-
this.#stmtInsertChunkTrigram.run(chunk.title, chunk.content, sourceId, ct, null,
|
|
826
|
+
this.#stmtInsertChunk.run(chunk.title, chunk.content, sourceId, ct, null, sessionIdCol, eventIdCol, now);
|
|
827
|
+
this.#stmtInsertChunkTrigram.run(chunk.title, chunk.content, sourceId, ct, null, sessionIdCol, eventIdCol, now);
|
|
824
828
|
}
|
|
825
829
|
return sourceId;
|
|
826
830
|
});
|
|
@@ -38,6 +38,21 @@ export interface DiscoverOptions {
|
|
|
38
38
|
platform?: NodeJS.Platform;
|
|
39
39
|
/** Test injection point — defaults to `child_process.execFileSync`. */
|
|
40
40
|
runCommand?: RunCommand;
|
|
41
|
+
/**
|
|
42
|
+
* When true, only return pids whose parent (ppid) is the SAME as the
|
|
43
|
+
* caller's own ppid (i.e. siblings under the same host process).
|
|
44
|
+
*
|
|
45
|
+
* Used by the startup sweep (#565) so an opencode-spawned MCP child
|
|
46
|
+
* only reaps OTHER opencode-spawned MCP children, never the children
|
|
47
|
+
* of a different opencode/Claude host running in parallel.
|
|
48
|
+
*
|
|
49
|
+
* Requires a way to read each pid's ppid. Defaults to a `ps -o ppid=`
|
|
50
|
+
* probe on POSIX and PowerShell `Get-CimInstance` on Windows. Set
|
|
51
|
+
* `readPpid` to inject in tests.
|
|
52
|
+
*/
|
|
53
|
+
sameParentOnly?: boolean;
|
|
54
|
+
/** Test injection — read ppid for a given pid. Defaults to platform probe. */
|
|
55
|
+
readPpid?: (pid: number) => number;
|
|
41
56
|
}
|
|
42
57
|
export interface KillOptions {
|
|
43
58
|
pids: readonly number[];
|
|
@@ -77,3 +92,28 @@ export declare function discoverSiblingMcpPids(opts: DiscoverOptions): number[];
|
|
|
77
92
|
* counted — they were not ours to kill.
|
|
78
93
|
*/
|
|
79
94
|
export declare function killSiblingMcpServers(opts: KillOptions): Promise<KillReport>;
|
|
95
|
+
/**
|
|
96
|
+
* Startup-time sibling sweep (#565).
|
|
97
|
+
*
|
|
98
|
+
* Discovers any context-mode MCP server pids that share OUR parent process
|
|
99
|
+
* (i.e. other MCP children of the same host like `opencode serve`) and
|
|
100
|
+
* terminates them. The intent is "exactly one MCP child per host" — when a
|
|
101
|
+
* new MCP client spawns inside an opencode host that already has 25 stale
|
|
102
|
+
* idle siblings, this sweep reclaims them at boot rather than waiting for
|
|
103
|
+
* the idle timeout to fire on each one independently.
|
|
104
|
+
*
|
|
105
|
+
* Gated by env (default-on but easy to disable):
|
|
106
|
+
*
|
|
107
|
+
* CONTEXT_MODE_STARTUP_SWEEP=0 → disabled
|
|
108
|
+
* CONTEXT_MODE_STARTUP_SWEEP=1 → enabled (default)
|
|
109
|
+
*
|
|
110
|
+
* Safety:
|
|
111
|
+
* - `sameParentOnly: true` — never touches MCP children of a different host.
|
|
112
|
+
* - Best-effort throughout: failures never block server startup.
|
|
113
|
+
* - Composes with the idle-timeout path: if a sibling is actively in use
|
|
114
|
+
* by another session, the parent process will simply spawn a new MCP
|
|
115
|
+
* child on its next request. The cost is one cold-start (~1–3 s) for
|
|
116
|
+
* that session, which is identical to opencode's existing behaviour
|
|
117
|
+
* of spawning a fresh MCP child per session anyway.
|
|
118
|
+
*/
|
|
119
|
+
export declare function startupSiblingSweep(env?: NodeJS.ProcessEnv): Promise<KillReport>;
|
|
@@ -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
|
+
}
|