context-mode 1.0.161 → 1.0.163
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/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +142 -28
- package/bin/statusline.mjs +24 -4
- package/build/adapters/antigravity/index.d.ts +1 -1
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +341 -0
- package/build/adapters/claude-code/hooks.d.ts +1 -0
- package/build/adapters/claude-code/hooks.js +3 -0
- package/build/adapters/claude-code/index.js +24 -5
- package/build/adapters/client-map.js +5 -0
- package/build/adapters/codex/hooks.d.ts +5 -1
- package/build/adapters/codex/hooks.js +5 -1
- package/build/adapters/codex/index.d.ts +9 -1
- package/build/adapters/codex/index.js +87 -5
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/detect.d.ts +1 -1
- package/build/adapters/detect.js +71 -3
- package/build/adapters/openclaw/mcp-tools.js +1 -1
- package/build/adapters/opencode/index.js +31 -17
- package/build/adapters/opencode/zod3tov4.js +27 -6
- package/build/adapters/pi/extension.d.ts +2 -12
- package/build/adapters/pi/extension.js +114 -96
- package/build/adapters/types.d.ts +5 -4
- package/build/adapters/types.js +4 -3
- package/build/cache-heal.d.ts +48 -0
- package/build/cache-heal.js +150 -0
- package/build/cli.js +37 -97
- package/build/executor.d.ts +25 -0
- package/build/executor.js +143 -22
- package/build/opencode-plugin.js +5 -2
- package/build/routing-block.d.ts +8 -0
- package/build/routing-block.js +86 -0
- package/build/runtime.d.ts +0 -36
- package/build/runtime.js +107 -27
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/security.d.ts +8 -3
- package/build/security.js +155 -29
- package/build/server.d.ts +14 -0
- package/build/server.js +368 -350
- package/build/session/analytics.d.ts +8 -8
- package/build/session/analytics.js +18 -13
- package/build/session/db.d.ts +1 -0
- package/build/session/db.js +37 -4
- package/build/session/extract.d.ts +46 -0
- package/build/session/extract.js +764 -13
- package/build/session/project-attribution.js +14 -0
- package/build/store.d.ts +1 -1
- package/build/store.js +139 -25
- package/build/tool-naming.d.ts +4 -0
- package/build/tool-naming.js +24 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/cli.bundle.mjs +260 -254
- package/configs/antigravity/GEMINI.md +2 -2
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +2 -2
- package/configs/codex/AGENTS.md +2 -2
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/gemini-cli/GEMINI.md +2 -2
- package/configs/jetbrains-copilot/copilot-instructions.md +2 -2
- package/configs/kilo/AGENTS.md +2 -2
- package/configs/kiro/KIRO.md +2 -2
- package/configs/omp/SYSTEM.md +2 -2
- package/configs/openclaw/AGENTS.md +2 -2
- package/configs/opencode/AGENTS.md +2 -2
- package/configs/qwen-code/QWEN.md +2 -2
- package/configs/vscode-copilot/copilot-instructions.md +2 -2
- package/configs/zed/AGENTS.md +2 -2
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/codex/pretooluse.mjs +14 -4
- package/hooks/codex/stop.mjs +12 -4
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +158 -7
- package/hooks/core/mcp-ready.mjs +37 -8
- package/hooks/core/routing.mjs +94 -8
- package/hooks/core/tool-naming.mjs +3 -0
- package/hooks/hooks.json +12 -1
- package/hooks/pretooluse.mjs +6 -2
- package/hooks/routing-block.mjs +2 -2
- package/hooks/security.bundle.mjs +2 -1
- package/hooks/session-db.bundle.mjs +11 -7
- package/hooks/session-directive.mjs +88 -20
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/session-helpers.mjs +21 -0
- package/hooks/session-loaders.mjs +8 -5
- package/hooks/sessionstart.mjs +53 -7
- package/hooks/stop.mjs +49 -0
- package/hooks/userpromptsubmit.mjs +9 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -10
- package/scripts/install-antigravity-cli-plugin.mjs +141 -0
- package/server.bundle.mjs +214 -205
- package/skills/ctx-insight/SKILL.md +12 -17
- package/build/util/db-lock.d.ts +0 -65
- package/build/util/db-lock.js +0 -166
- package/insight/index.html +0 -13
- package/insight/package.json +0 -55
- package/insight/server.mjs +0 -1265
- package/insight/src/components/analytics.tsx +0 -112
- package/insight/src/components/ui/badge.tsx +0 -52
- package/insight/src/components/ui/button.tsx +0 -58
- package/insight/src/components/ui/card.tsx +0 -103
- package/insight/src/components/ui/chart.tsx +0 -371
- package/insight/src/components/ui/collapsible.tsx +0 -19
- package/insight/src/components/ui/input.tsx +0 -20
- package/insight/src/components/ui/progress.tsx +0 -83
- package/insight/src/components/ui/scroll-area.tsx +0 -55
- package/insight/src/components/ui/separator.tsx +0 -23
- package/insight/src/components/ui/table.tsx +0 -114
- package/insight/src/components/ui/tabs.tsx +0 -82
- package/insight/src/components/ui/tooltip.tsx +0 -64
- package/insight/src/lib/api.ts +0 -144
- package/insight/src/lib/utils.ts +0 -6
- package/insight/src/main.tsx +0 -22
- package/insight/src/routeTree.gen.ts +0 -189
- package/insight/src/router.tsx +0 -19
- package/insight/src/routes/__root.tsx +0 -55
- package/insight/src/routes/enterprise.tsx +0 -316
- package/insight/src/routes/index.tsx +0 -1482
- package/insight/src/routes/knowledge.tsx +0 -221
- package/insight/src/routes/knowledge_.$dbHash.$sourceId.tsx +0 -137
- package/insight/src/routes/search.tsx +0 -97
- package/insight/src/routes/sessions.tsx +0 -179
- package/insight/src/routes/sessions_.$dbHash.$sessionId.tsx +0 -181
- package/insight/src/styles.css +0 -104
- package/insight/tsconfig.json +0 -29
- package/insight/vite.config.ts +0 -19
|
@@ -194,14 +194,28 @@ export function resolveProjectAttribution(event, context) {
|
|
|
194
194
|
export function resolveProjectAttributions(events, context) {
|
|
195
195
|
const out = [];
|
|
196
196
|
let lastKnown = context.lastKnownProjectDir ? normalizePath(context.lastKnownProjectDir) : "";
|
|
197
|
+
// v1.0.162 Bug 8 — track whether an in-batch CWD-level event has explicitly
|
|
198
|
+
// re-scoped the project. When extract.ts emits a cwd event for `cd /projB`
|
|
199
|
+
// or `git -C /projB ...`, subsequent path-less events in the same batch
|
|
200
|
+
// (e.g. the git operation event itself) currently fall back to the hook's
|
|
201
|
+
// inputProjectDir, which still points at the session startup cwd. The
|
|
202
|
+
// user's INTENTIONAL scoping should win over the hook's startup cwd —
|
|
203
|
+
// shadow inputProjectDir with the carried-forward lastKnown once a high-
|
|
204
|
+
// confidence cwd event has fired in this batch.
|
|
205
|
+
let inBatchCwdScope = false;
|
|
197
206
|
for (const ev of events) {
|
|
207
|
+
const effectiveInputCwd = inBatchCwdScope ? lastKnown : context.inputProjectDir;
|
|
198
208
|
const attribution = resolveProjectAttribution(ev, {
|
|
199
209
|
...context,
|
|
210
|
+
inputProjectDir: effectiveInputCwd,
|
|
200
211
|
lastKnownProjectDir: lastKnown || context.lastKnownProjectDir || null,
|
|
201
212
|
});
|
|
202
213
|
out.push(attribution);
|
|
203
214
|
if (attribution.projectDir && attribution.confidence >= ATTRIBUTION_CONFIDENCE.CARRY_FORWARD_THRESHOLD) {
|
|
204
215
|
lastKnown = attribution.projectDir;
|
|
216
|
+
if (attribution.confidence >= ATTRIBUTION_CONFIDENCE.CWD_EVENT) {
|
|
217
|
+
inBatchCwdScope = true;
|
|
218
|
+
}
|
|
205
219
|
}
|
|
206
220
|
}
|
|
207
221
|
return out;
|
package/build/store.d.ts
CHANGED
|
@@ -88,7 +88,7 @@ export declare class ContentStore {
|
|
|
88
88
|
indexPlainText(content: string, source: string, linesPerChunk?: number, attribution?: {
|
|
89
89
|
sessionId?: string;
|
|
90
90
|
eventId?: string;
|
|
91
|
-
}): IndexResult;
|
|
91
|
+
}, maxChunkBytes?: number): IndexResult;
|
|
92
92
|
/**
|
|
93
93
|
* Index JSON content by walking the object tree and using key paths
|
|
94
94
|
* as chunk titles (analogous to heading hierarchy in markdown). Objects
|
package/build/store.js
CHANGED
|
@@ -108,6 +108,20 @@ function maxEditDistance(wordLength) {
|
|
|
108
108
|
// length normalization and produce unwieldy search results. Split at paragraph
|
|
109
109
|
// boundaries when a chunk exceeds this cap.
|
|
110
110
|
const MAX_CHUNK_BYTES = 4096;
|
|
111
|
+
// Blank-line sectioning is used only for output that is *naturally* sectioned:
|
|
112
|
+
// at least a few sections, not an unbounded explosion, and no single section so
|
|
113
|
+
// large that the split is clearly not the real structure (those fall back to
|
|
114
|
+
// line-grouping). Sections that pass the heuristic but still exceed
|
|
115
|
+
// MAX_CHUNK_BYTES are sub-split so no persisted chunk breaks the cap.
|
|
116
|
+
const MIN_BLANK_LINE_SECTIONS = 3;
|
|
117
|
+
const MAX_BLANK_LINE_SECTIONS = 200;
|
|
118
|
+
const BLANK_SECTION_STRATEGY_MAX_BYTES = 5000;
|
|
119
|
+
// Number of leading characters of a chunk's first line used as its title.
|
|
120
|
+
const CHUNK_TITLE_MAX_CHARS = 80;
|
|
121
|
+
// When byte-splitting an oversized single line, prefer to break at a whitespace
|
|
122
|
+
// boundary for readability — but only if that boundary is past this fraction of
|
|
123
|
+
// the slice, otherwise we'd waste too much of the byte budget.
|
|
124
|
+
const WHITESPACE_BREAK_RATIO = 0.5;
|
|
111
125
|
// ─────────────────────────────────────────────────────────
|
|
112
126
|
// ContentStore
|
|
113
127
|
// ─────────────────────────────────────────────────────────
|
|
@@ -820,11 +834,11 @@ export class ContentStore {
|
|
|
820
834
|
* into fixed-size line groups. Unlike markdown indexing, this does not
|
|
821
835
|
* look for headings — it chunks by line count with overlap.
|
|
822
836
|
*/
|
|
823
|
-
indexPlainText(content, source, linesPerChunk = 20, attribution) {
|
|
837
|
+
indexPlainText(content, source, linesPerChunk = 20, attribution, maxChunkBytes = MAX_CHUNK_BYTES) {
|
|
824
838
|
if (!content || content.trim().length === 0) {
|
|
825
839
|
return this.#insertChunks([], source, "", undefined, undefined, attribution);
|
|
826
840
|
}
|
|
827
|
-
const chunks = this.#chunkPlainText(content, linesPerChunk);
|
|
841
|
+
const chunks = this.#chunkPlainText(content, linesPerChunk, maxChunkBytes);
|
|
828
842
|
return withRetry(() => this.#insertChunks(chunks.map((c) => ({ ...c, hasCode: false })), source, content, undefined, undefined, attribution));
|
|
829
843
|
}
|
|
830
844
|
// ── Index JSON ──
|
|
@@ -837,19 +851,19 @@ export class ContentStore {
|
|
|
837
851
|
*/
|
|
838
852
|
indexJSON(content, source, maxChunkBytes = MAX_CHUNK_BYTES, attribution) {
|
|
839
853
|
if (!content || content.trim().length === 0) {
|
|
840
|
-
return this.indexPlainText("", source, undefined, attribution);
|
|
854
|
+
return this.indexPlainText("", source, undefined, attribution, maxChunkBytes);
|
|
841
855
|
}
|
|
842
856
|
let parsed;
|
|
843
857
|
try {
|
|
844
858
|
parsed = JSON.parse(content);
|
|
845
859
|
}
|
|
846
860
|
catch {
|
|
847
|
-
return this.indexPlainText(content, source, undefined, attribution);
|
|
861
|
+
return this.indexPlainText(content, source, undefined, attribution, maxChunkBytes);
|
|
848
862
|
}
|
|
849
863
|
const chunks = [];
|
|
850
864
|
this.#walkJSON(parsed, [], chunks, maxChunkBytes);
|
|
851
865
|
if (chunks.length === 0) {
|
|
852
|
-
return this.indexPlainText(content, source, undefined, attribution);
|
|
866
|
+
return this.indexPlainText(content, source, undefined, attribution, maxChunkBytes);
|
|
853
867
|
}
|
|
854
868
|
return withRetry(() => this.#insertChunks(chunks, source, content, undefined, undefined, attribution));
|
|
855
869
|
}
|
|
@@ -1448,27 +1462,119 @@ export class ContentStore {
|
|
|
1448
1462
|
flush();
|
|
1449
1463
|
return chunks;
|
|
1450
1464
|
}
|
|
1451
|
-
|
|
1465
|
+
/**
|
|
1466
|
+
* Return the largest prefix of `str` whose UTF-8 byte length does not exceed
|
|
1467
|
+
* `maxBytes`, walking by Unicode code point so multibyte sequences (CJK) and
|
|
1468
|
+
* surrogate pairs (emoji) are never cut mid-character. Guarantees forward
|
|
1469
|
+
* progress: if even the first code point exceeds `maxBytes`, it is still
|
|
1470
|
+
* returned whole (a 1-4 byte overshoot beats an infinite loop).
|
|
1471
|
+
*/
|
|
1472
|
+
#byteCappedPrefix(str, maxBytes) {
|
|
1473
|
+
if (Buffer.byteLength(str) <= maxBytes)
|
|
1474
|
+
return str;
|
|
1475
|
+
let prefix = "";
|
|
1476
|
+
let bytes = 0;
|
|
1477
|
+
for (const char of str) {
|
|
1478
|
+
const charBytes = Buffer.byteLength(char);
|
|
1479
|
+
if (bytes + charBytes > maxBytes)
|
|
1480
|
+
break;
|
|
1481
|
+
prefix += char;
|
|
1482
|
+
bytes += charBytes;
|
|
1483
|
+
}
|
|
1484
|
+
// Defensive: a single code point wider than the cap (only possible with a
|
|
1485
|
+
// pathologically small maxBytes) still advances by one character.
|
|
1486
|
+
if (prefix.length === 0)
|
|
1487
|
+
return [...str][0] ?? "";
|
|
1488
|
+
return prefix;
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Split a single oversized plain-text chunk into byte-capped sub-chunks
|
|
1492
|
+
* by accumulating lines until the byte count would exceed maxChunkBytes.
|
|
1493
|
+
* Falls back to byte-accurate splitting for extremely long single lines.
|
|
1494
|
+
*/
|
|
1495
|
+
#splitOversizedPlainChunk(lines, titlePrefix, maxChunkBytes) {
|
|
1496
|
+
const subChunks = [];
|
|
1497
|
+
let accumulator = [];
|
|
1498
|
+
let partIndex = 1;
|
|
1499
|
+
const flushAccumulator = () => {
|
|
1500
|
+
if (accumulator.length === 0)
|
|
1501
|
+
return;
|
|
1502
|
+
const content = accumulator.join("\n");
|
|
1503
|
+
const partTitle = partIndex === 1 ? titlePrefix : `${titlePrefix} (${partIndex})`;
|
|
1504
|
+
subChunks.push({ title: partTitle, content });
|
|
1505
|
+
partIndex++;
|
|
1506
|
+
accumulator = [];
|
|
1507
|
+
};
|
|
1508
|
+
for (const line of lines) {
|
|
1509
|
+
// If a single line itself exceeds the cap (even as first line),
|
|
1510
|
+
// split it by character before accumulating
|
|
1511
|
+
if (Buffer.byteLength(line) > maxChunkBytes) {
|
|
1512
|
+
flushAccumulator();
|
|
1513
|
+
// Split the long line into byte-capped pieces
|
|
1514
|
+
let remaining = line;
|
|
1515
|
+
let linePart = 1;
|
|
1516
|
+
while (remaining.length > 0) {
|
|
1517
|
+
// Byte-accurate slice: never exceeds the cap, never cuts a multibyte
|
|
1518
|
+
// character (CJK) or surrogate pair (emoji) in half.
|
|
1519
|
+
let slice = this.#byteCappedPrefix(remaining, maxChunkBytes);
|
|
1520
|
+
// Try to break at a whitespace boundary near the end for readability,
|
|
1521
|
+
// but only when text remains after this slice.
|
|
1522
|
+
if (slice.length < remaining.length) {
|
|
1523
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
1524
|
+
const lastNewline = slice.lastIndexOf("\n");
|
|
1525
|
+
const breakPoint = Math.max(lastSpace, lastNewline);
|
|
1526
|
+
if (breakPoint > slice.length * WHITESPACE_BREAK_RATIO) {
|
|
1527
|
+
slice = slice.slice(0, breakPoint);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
const linePartTitle = partIndex === 1 && linePart === 1
|
|
1531
|
+
? titlePrefix
|
|
1532
|
+
: `${titlePrefix} (${partIndex}.${linePart})`;
|
|
1533
|
+
subChunks.push({ title: linePartTitle, content: slice });
|
|
1534
|
+
remaining = remaining.slice(slice.length);
|
|
1535
|
+
linePart++;
|
|
1536
|
+
partIndex++;
|
|
1537
|
+
}
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
const candidate = accumulator.length > 0
|
|
1541
|
+
? accumulator.join("\n") + "\n" + line
|
|
1542
|
+
: line;
|
|
1543
|
+
// If adding this line would exceed the cap, flush accumulator first
|
|
1544
|
+
if (Buffer.byteLength(candidate) > maxChunkBytes && accumulator.length > 0) {
|
|
1545
|
+
flushAccumulator();
|
|
1546
|
+
}
|
|
1547
|
+
accumulator.push(line);
|
|
1548
|
+
}
|
|
1549
|
+
flushAccumulator();
|
|
1550
|
+
return subChunks;
|
|
1551
|
+
}
|
|
1552
|
+
#chunkPlainText(text, linesPerChunk, maxChunkBytes = MAX_CHUNK_BYTES) {
|
|
1452
1553
|
// Try blank-line splitting first for naturally-sectioned output
|
|
1453
1554
|
const sections = text.split(/\n\s*\n/);
|
|
1454
|
-
if (sections.length >=
|
|
1455
|
-
sections.length <=
|
|
1456
|
-
sections.every((s) => Buffer.byteLength(s) <
|
|
1457
|
-
return sections
|
|
1458
|
-
.map((section, i) => {
|
|
1555
|
+
if (sections.length >= MIN_BLANK_LINE_SECTIONS &&
|
|
1556
|
+
sections.length <= MAX_BLANK_LINE_SECTIONS &&
|
|
1557
|
+
sections.every((s) => Buffer.byteLength(s) < BLANK_SECTION_STRATEGY_MAX_BYTES)) {
|
|
1558
|
+
return sections.flatMap((section, i) => {
|
|
1459
1559
|
const trimmed = section.trim();
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1560
|
+
if (trimmed.length === 0)
|
|
1561
|
+
return [];
|
|
1562
|
+
const title = trimmed.split("\n")[0].slice(0, CHUNK_TITLE_MAX_CHARS) || `Section ${i + 1}`;
|
|
1563
|
+
// A section may pass the strategy guard yet still exceed the byte cap
|
|
1564
|
+
// (4097–4999B band): sub-split it so no stored chunk breaks the cap.
|
|
1565
|
+
if (Buffer.byteLength(trimmed) <= maxChunkBytes) {
|
|
1566
|
+
return [{ title, content: trimmed }];
|
|
1567
|
+
}
|
|
1568
|
+
return this.#splitOversizedPlainChunk(trimmed.split("\n"), title, maxChunkBytes);
|
|
1569
|
+
});
|
|
1467
1570
|
}
|
|
1468
1571
|
const lines = text.split("\n");
|
|
1469
|
-
// Small enough for a single chunk
|
|
1572
|
+
// Small enough for a single chunk — but still enforce byte cap
|
|
1470
1573
|
if (lines.length <= linesPerChunk) {
|
|
1471
|
-
|
|
1574
|
+
if (Buffer.byteLength(text) <= maxChunkBytes) {
|
|
1575
|
+
return [{ title: "Output", content: text }];
|
|
1576
|
+
}
|
|
1577
|
+
return this.#splitOversizedPlainChunk(lines, "Output", maxChunkBytes);
|
|
1472
1578
|
}
|
|
1473
1579
|
// Fixed-size line groups with 2-line overlap
|
|
1474
1580
|
const chunks = [];
|
|
@@ -1480,11 +1586,19 @@ export class ContentStore {
|
|
|
1480
1586
|
break;
|
|
1481
1587
|
const startLine = i + 1;
|
|
1482
1588
|
const endLine = Math.min(i + slice.length, lines.length);
|
|
1483
|
-
const firstLine = slice[0]?.trim().slice(0,
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1589
|
+
const firstLine = slice[0]?.trim().slice(0, CHUNK_TITLE_MAX_CHARS);
|
|
1590
|
+
const joined = slice.join("\n");
|
|
1591
|
+
// Enforce byte cap: sub-split oversized line-group chunks
|
|
1592
|
+
if (Buffer.byteLength(joined) <= maxChunkBytes) {
|
|
1593
|
+
chunks.push({
|
|
1594
|
+
title: firstLine || `Lines ${startLine}-${endLine}`,
|
|
1595
|
+
content: joined,
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
const subChunks = this.#splitOversizedPlainChunk(slice, firstLine || `Lines ${startLine}-${endLine}`, maxChunkBytes);
|
|
1600
|
+
chunks.push(...subChunks);
|
|
1601
|
+
}
|
|
1488
1602
|
}
|
|
1489
1603
|
return chunks;
|
|
1490
1604
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const TOOL_PREFIXES = {
|
|
2
|
+
"claude-code": (tool) => `mcp__plugin_context-mode_context-mode__${tool}`,
|
|
3
|
+
"gemini-cli": (tool) => `mcp__context-mode__${tool}`,
|
|
4
|
+
"antigravity": (tool) => `mcp__context-mode__${tool}`,
|
|
5
|
+
"opencode": (tool) => `context-mode_${tool}`,
|
|
6
|
+
"kilo": (tool) => `context-mode_${tool}`,
|
|
7
|
+
"vscode-copilot": (tool) => `context-mode_${tool}`,
|
|
8
|
+
"jetbrains-copilot": (tool) => `context-mode_${tool}`,
|
|
9
|
+
"kiro": (tool) => `@context-mode/${tool}`,
|
|
10
|
+
"zed": (tool) => `mcp:context-mode:${tool}`,
|
|
11
|
+
"cursor": (tool) => tool,
|
|
12
|
+
"codex": (tool) => tool,
|
|
13
|
+
"openclaw": (tool) => tool,
|
|
14
|
+
"pi": (tool) => tool,
|
|
15
|
+
"qwen-code": (tool) => `mcp__context-mode__${tool}`,
|
|
16
|
+
};
|
|
17
|
+
export function getToolName(platform, bareTool) {
|
|
18
|
+
const fn = TOOL_PREFIXES[platform] || TOOL_PREFIXES["claude-code"];
|
|
19
|
+
return fn(bareTool);
|
|
20
|
+
}
|
|
21
|
+
export function createToolNamer(platform) {
|
|
22
|
+
return (bareTool) => getToolName(platform, bareTool);
|
|
23
|
+
}
|
|
24
|
+
export const KNOWN_PLATFORMS = Object.keys(TOOL_PREFIXES);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* util/jsonc — string-aware JSONC comment + trailing-comma stripping and a
|
|
3
|
+
* tolerant parse. Several agent CLIs ship config files as JSONC (VS Code
|
|
4
|
+
* `mcp.json`, Zed `settings.json`), so a strict `JSON.parse` false-fails on a
|
|
5
|
+
* perfectly valid commented file. Use `parseJsonc` whenever reading a
|
|
6
|
+
* platform config we did not write ourselves.
|
|
7
|
+
*/
|
|
8
|
+
/** Strip `//` line + `/* */` block comments and trailing commas, string-aware. */
|
|
9
|
+
export declare function stripJsonComments(str: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Parse JSON or JSONC. Tries strict `JSON.parse` first (fast, exact), then a
|
|
12
|
+
* comment/trailing-comma-stripped parse. Returns `undefined` when both fail.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseJsonc<T = unknown>(raw: string): T | undefined;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* util/jsonc — string-aware JSONC comment + trailing-comma stripping and a
|
|
3
|
+
* tolerant parse. Several agent CLIs ship config files as JSONC (VS Code
|
|
4
|
+
* `mcp.json`, Zed `settings.json`), so a strict `JSON.parse` false-fails on a
|
|
5
|
+
* perfectly valid commented file. Use `parseJsonc` whenever reading a
|
|
6
|
+
* platform config we did not write ourselves.
|
|
7
|
+
*/
|
|
8
|
+
/** Strip `//` line + `/* */` block comments and trailing commas, string-aware. */
|
|
9
|
+
export function stripJsonComments(str) {
|
|
10
|
+
let out = "";
|
|
11
|
+
let inString = false;
|
|
12
|
+
let escaped = false;
|
|
13
|
+
let inBlockComment = false;
|
|
14
|
+
for (let i = 0; i < str.length; i++) {
|
|
15
|
+
const c = str[i];
|
|
16
|
+
const next = str[i + 1];
|
|
17
|
+
if (inBlockComment) {
|
|
18
|
+
if (c === "*" && next === "/") {
|
|
19
|
+
inBlockComment = false;
|
|
20
|
+
i++;
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (escaped) {
|
|
25
|
+
out += c;
|
|
26
|
+
escaped = false;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (c === "\\") {
|
|
30
|
+
out += c;
|
|
31
|
+
escaped = inString;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (c === '"') {
|
|
35
|
+
inString = !inString;
|
|
36
|
+
out += c;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!inString && c === "/" && next === "/") {
|
|
40
|
+
while (i < str.length && str[i] !== "\n")
|
|
41
|
+
i++;
|
|
42
|
+
if (i < str.length)
|
|
43
|
+
out += "\n";
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!inString && c === "/" && next === "*") {
|
|
47
|
+
inBlockComment = true;
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
out += c;
|
|
52
|
+
}
|
|
53
|
+
// Trailing-comma removal, string-aware. The scan above already removed
|
|
54
|
+
// comments, so this second pass over `out` only needs to track string state:
|
|
55
|
+
// a comma is "trailing" when the next significant char is `}` or `]`. Doing
|
|
56
|
+
// it here — instead of a post-hoc trailing-comma regex over the whole string —
|
|
57
|
+
// preserves commas inside string literals (e.g. "[1, ]"), which that regex
|
|
58
|
+
// silently corrupted to "[1 ]". See #787 review.
|
|
59
|
+
let result = "";
|
|
60
|
+
let inStr = false;
|
|
61
|
+
let esc = false;
|
|
62
|
+
for (let i = 0; i < out.length; i++) {
|
|
63
|
+
const c = out[i];
|
|
64
|
+
if (esc) {
|
|
65
|
+
result += c;
|
|
66
|
+
esc = false;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (c === "\\") {
|
|
70
|
+
result += c;
|
|
71
|
+
esc = inStr;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (c === '"') {
|
|
75
|
+
inStr = !inStr;
|
|
76
|
+
result += c;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!inStr && c === ",") {
|
|
80
|
+
let j = i + 1;
|
|
81
|
+
while (j < out.length && (out[j] === " " || out[j] === "\t" || out[j] === "\r" || out[j] === "\n"))
|
|
82
|
+
j++;
|
|
83
|
+
if (out[j] === "}" || out[j] === "]")
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
result += c;
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parse JSON or JSONC. Tries strict `JSON.parse` first (fast, exact), then a
|
|
92
|
+
* comment/trailing-comma-stripped parse. Returns `undefined` when both fail.
|
|
93
|
+
*/
|
|
94
|
+
export function parseJsonc(raw) {
|
|
95
|
+
for (const candidate of [raw, stripJsonComments(raw)]) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(candidate);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* try next */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|