context-mode 1.0.69 → 1.0.71
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/server.js +88 -0
- package/build/session/analytics.d.ts +3 -3
- package/build/session/analytics.js +76 -43
- package/cli.bundle.mjs +121 -105
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +92 -76
|
@@ -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.71"
|
|
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.71",
|
|
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.71",
|
|
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.71",
|
|
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.71",
|
|
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/build/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { existsSync, unlinkSync, readdirSync, readFileSync, rmSync, mkdirSync }
|
|
|
7
7
|
import { join, dirname, resolve } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { homedir, tmpdir } from "node:os";
|
|
10
|
+
import { request as httpsRequest } from "node:https";
|
|
10
11
|
import { z } from "zod";
|
|
11
12
|
import { PolyglotExecutor } from "./executor.js";
|
|
12
13
|
import { ContentStore, cleanupStaleDBs, cleanupStaleContentDBs } from "./store.js";
|
|
@@ -180,7 +181,72 @@ const sessionStats = {
|
|
|
180
181
|
cacheBytesSaved: 0, // bytes avoided by TTL cache hits
|
|
181
182
|
sessionStart: Date.now(),
|
|
182
183
|
};
|
|
184
|
+
// ── Version outdated warning ──────────────────────────────────────────────
|
|
185
|
+
// Non-blocking npm check at startup. trackResponse prepends warning
|
|
186
|
+
// using a burst cadence: 3 warnings → 1h silent → 3 warnings → repeat.
|
|
187
|
+
let _latestVersion = null;
|
|
188
|
+
let _warningBurstCount = 0;
|
|
189
|
+
let _lastBurstStart = 0;
|
|
190
|
+
const VERSION_BURST_SIZE = 3;
|
|
191
|
+
const VERSION_SILENT_MS = 60 * 60 * 1000; // 1 hour
|
|
192
|
+
async function fetchLatestVersion() {
|
|
193
|
+
return new Promise((res) => {
|
|
194
|
+
const req = httpsRequest("https://registry.npmjs.org/context-mode/latest", { headers: { Connection: "close" } }, (resp) => {
|
|
195
|
+
let raw = "";
|
|
196
|
+
resp.on("data", (chunk) => { raw += chunk; });
|
|
197
|
+
resp.on("end", () => {
|
|
198
|
+
try {
|
|
199
|
+
const data = JSON.parse(raw);
|
|
200
|
+
res(data.version ?? "unknown");
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
res("unknown");
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
req.on("error", () => res("unknown"));
|
|
208
|
+
req.setTimeout(5000, () => { req.destroy(); res("unknown"); });
|
|
209
|
+
req.end();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function getUpgradeHint() {
|
|
213
|
+
const name = _detectedAdapter?.name;
|
|
214
|
+
if (name === "Claude Code")
|
|
215
|
+
return "/ctx-upgrade";
|
|
216
|
+
if (name === "OpenClaw")
|
|
217
|
+
return "npm run install:openclaw";
|
|
218
|
+
if (name === "Pi")
|
|
219
|
+
return "npm run build";
|
|
220
|
+
return "npm update -g context-mode";
|
|
221
|
+
}
|
|
222
|
+
function isOutdated() {
|
|
223
|
+
if (!_latestVersion || _latestVersion === "unknown")
|
|
224
|
+
return false;
|
|
225
|
+
return _latestVersion !== VERSION;
|
|
226
|
+
}
|
|
227
|
+
function shouldShowVersionWarning() {
|
|
228
|
+
if (!isOutdated())
|
|
229
|
+
return false;
|
|
230
|
+
const now = Date.now();
|
|
231
|
+
// Start of a new burst?
|
|
232
|
+
if (_warningBurstCount >= VERSION_BURST_SIZE) {
|
|
233
|
+
if (now - _lastBurstStart < VERSION_SILENT_MS)
|
|
234
|
+
return false; // still silent
|
|
235
|
+
_warningBurstCount = 0; // silence over, reset burst
|
|
236
|
+
}
|
|
237
|
+
if (_warningBurstCount === 0)
|
|
238
|
+
_lastBurstStart = now;
|
|
239
|
+
_warningBurstCount++;
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
183
242
|
function trackResponse(toolName, response) {
|
|
243
|
+
// Prepend version outdated warning if needed
|
|
244
|
+
if (shouldShowVersionWarning() && response.content.length > 0) {
|
|
245
|
+
const hint = getUpgradeHint();
|
|
246
|
+
response.content[0].text =
|
|
247
|
+
`⚠️ context-mode v${VERSION} outdated → v${_latestVersion} available. Upgrade: ${hint}\n\n` +
|
|
248
|
+
response.content[0].text;
|
|
249
|
+
}
|
|
184
250
|
const bytes = response.content.reduce((sum, c) => sum + Buffer.byteLength(c.text), 0);
|
|
185
251
|
sessionStats.calls[toolName] = (sessionStats.calls[toolName] || 0) + 1;
|
|
186
252
|
sessionStats.bytesReturned[toolName] =
|
|
@@ -465,6 +531,19 @@ server.registerTool("ctx_execute", {
|
|
|
465
531
|
// The closure approach (function(__cm_req){ var require=...; })(require) correctly
|
|
466
532
|
// shadows the CJS require for all code inside, including __cm_main().
|
|
467
533
|
instrumentedCode = `
|
|
534
|
+
// FS read instrumentation — count bytes read via fs.readFileSync/readFile
|
|
535
|
+
let __cm_fs=0;
|
|
536
|
+
process.on('exit',()=>{if(__cm_fs>0)try{process.stderr.write('__CM_FS__:'+__cm_fs+'\\n')}catch{}});
|
|
537
|
+
(function(){
|
|
538
|
+
try{
|
|
539
|
+
var f=typeof require!=='undefined'?require('fs'):null;
|
|
540
|
+
if(!f)return;
|
|
541
|
+
var ors=f.readFileSync;
|
|
542
|
+
f.readFileSync=function(){var r=ors.apply(this,arguments);if(Buffer.isBuffer(r))__cm_fs+=r.length;else if(typeof r==='string')__cm_fs+=Buffer.byteLength(r);return r;};
|
|
543
|
+
var orf=f.readFile;
|
|
544
|
+
if(orf)f.readFile=function(){var a=Array.from(arguments),cb=a.pop();orf.apply(this,a.concat([function(e,d){if(!e&&d){if(Buffer.isBuffer(d))__cm_fs+=d.length;else if(typeof d==='string')__cm_fs+=Buffer.byteLength(d);}cb(e,d);}]));};
|
|
545
|
+
}catch{}
|
|
546
|
+
})();
|
|
468
547
|
let __cm_net=0;
|
|
469
548
|
// Report network bytes on process exit — works with both promise and callback patterns.
|
|
470
549
|
// process.on('exit') fires after all I/O completes, unlike .finally() which fires
|
|
@@ -519,6 +598,12 @@ __cm_main().catch(e=>{console.error(e);process.exitCode=1});${background ? '\nse
|
|
|
519
598
|
// Clean the metric line from stderr
|
|
520
599
|
result.stderr = result.stderr.replace(/\n?__CM_NET__:\d+\n?/g, "");
|
|
521
600
|
}
|
|
601
|
+
// Parse sandbox FS read metrics from stderr
|
|
602
|
+
const fsMatch = result.stderr?.match(/__CM_FS__:(\d+)/);
|
|
603
|
+
if (fsMatch) {
|
|
604
|
+
sessionStats.bytesSandboxed += parseInt(fsMatch[1]);
|
|
605
|
+
result.stderr = result.stderr.replace(/\n?__CM_FS__:\d+\n?/g, "");
|
|
606
|
+
}
|
|
522
607
|
if (result.timedOut) {
|
|
523
608
|
const partialOutput = result.stdout?.trim();
|
|
524
609
|
if (result.backgrounded && partialOutput) {
|
|
@@ -1770,6 +1855,9 @@ async function main() {
|
|
|
1770
1855
|
}
|
|
1771
1856
|
}
|
|
1772
1857
|
catch { /* best effort — _detectedAdapter stays null, falls back to .claude */ }
|
|
1858
|
+
// Non-blocking version check — result stored for trackResponse warnings
|
|
1859
|
+
fetchLatestVersion().then(v => { if (v !== "unknown")
|
|
1860
|
+
_latestVersion = v; });
|
|
1773
1861
|
console.error(`Context Mode MCP server v${VERSION} running on stdio`);
|
|
1774
1862
|
console.error(`Detected runtimes:\n${getRuntimeSummary(runtimes)}`);
|
|
1775
1863
|
if (!hasBunRuntime()) {
|
|
@@ -373,9 +373,9 @@ export declare class AnalyticsEngine {
|
|
|
373
373
|
queryAll(runtimeStats: RuntimeStats): FullReport;
|
|
374
374
|
}
|
|
375
375
|
/**
|
|
376
|
-
* Render a FullReport as
|
|
376
|
+
* Render a FullReport as a marketing-friendly, outcome-focused session story.
|
|
377
377
|
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
378
|
+
* Framework: Persona -> Metric -> Evidence -> Action -> ROI
|
|
379
|
+
* The output tells a narrative instead of dumping raw numbers.
|
|
380
380
|
*/
|
|
381
381
|
export declare function formatReport(report: FullReport): string;
|
|
@@ -613,62 +613,95 @@ function kb(b) {
|
|
|
613
613
|
return `${(b / 1024).toFixed(1)}KB`;
|
|
614
614
|
}
|
|
615
615
|
/**
|
|
616
|
-
* Render a FullReport as
|
|
616
|
+
* Render a FullReport as a marketing-friendly, outcome-focused session story.
|
|
617
617
|
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
618
|
+
* Framework: Persona -> Metric -> Evidence -> Action -> ROI
|
|
619
|
+
* The output tells a narrative instead of dumping raw numbers.
|
|
620
620
|
*/
|
|
621
621
|
export function formatReport(report) {
|
|
622
|
-
const lines = [
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
// ── Feature 1: Context Window Protection ──
|
|
626
|
-
lines.push("", `### Context Window Protection`, "");
|
|
622
|
+
const lines = [];
|
|
623
|
+
// ── Hero: headline story ──
|
|
624
|
+
lines.push(`## Think in Code -- Session Report`);
|
|
627
625
|
if (report.savings.total_calls === 0) {
|
|
628
|
-
lines.push(
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
626
|
+
lines.push("", "No sandbox activity yet. Use ctx_execute or ctx_batch_execute to start saving context.", "", "---", "_Display this entire report as-is in your response. Do NOT summarize, collapse, or paraphrase any section._");
|
|
627
|
+
return lines.join("\n");
|
|
628
|
+
}
|
|
629
|
+
const totalProcessed = report.savings.kept_out + (report.cache ? report.cache.bytes_saved : 0);
|
|
630
|
+
const totalReturned = report.savings.total_bytes_returned;
|
|
631
|
+
const grandTotal = totalProcessed + totalReturned;
|
|
632
|
+
const savingsPercent = grandTotal > 0
|
|
633
|
+
? ((1 - totalReturned / grandTotal) * 100).toFixed(1)
|
|
634
|
+
: "0.0";
|
|
635
|
+
// Rough estimate: 4 bytes per token, ~2 min reading time per 1000 tokens
|
|
636
|
+
const tokensSaved = Math.round(totalProcessed / 4);
|
|
637
|
+
const extraMinutes = Math.round((tokensSaved / 1000) * 2);
|
|
638
|
+
lines.push("", `Your agent processed ${kb(totalProcessed)} of data.`, `Only ${kb(totalReturned)} entered your context window.`, "", `**Context saved: ${savingsPercent}% -- session extended by ~${extraMinutes} minutes**`);
|
|
639
|
+
// ── What happened ──
|
|
640
|
+
lines.push("", "### What happened", "");
|
|
641
|
+
// Count per-tool categories
|
|
642
|
+
const toolCallMap = new Map();
|
|
643
|
+
for (const t of report.savings.by_tool) {
|
|
644
|
+
toolCallMap.set(t.tool, t.calls);
|
|
645
|
+
}
|
|
646
|
+
const executeCount = (toolCallMap.get("ctx_execute") ?? 0) +
|
|
647
|
+
(toolCallMap.get("ctx_execute_file") ?? 0);
|
|
648
|
+
const batchCount = toolCallMap.get("ctx_batch_execute") ?? 0;
|
|
649
|
+
const searchCount = toolCallMap.get("ctx_search") ?? 0;
|
|
650
|
+
const fetchCount = toolCallMap.get("ctx_fetch_and_index") ?? 0;
|
|
651
|
+
const fileCount = executeCount + batchCount;
|
|
652
|
+
const networkCount = fetchCount;
|
|
653
|
+
const cacheCount = report.cache ? report.cache.hits : 0;
|
|
654
|
+
if (fileCount > 0) {
|
|
655
|
+
lines.push(`-> ${fileCount} file${fileCount !== 1 ? "s" : ""} analyzed in sandbox (never entered context)`);
|
|
656
|
+
}
|
|
657
|
+
if (networkCount > 0) {
|
|
658
|
+
lines.push(`-> ${networkCount} API call${networkCount !== 1 ? "s" : ""} sandboxed (responses indexed, not dumped)`);
|
|
659
|
+
}
|
|
660
|
+
if (searchCount > 0) {
|
|
661
|
+
lines.push(`-> ${searchCount} search quer${searchCount !== 1 ? "ies" : "y"} served from index`);
|
|
662
|
+
}
|
|
663
|
+
if (cacheCount > 0) {
|
|
664
|
+
lines.push(`-> ${cacheCount} repeat fetch${cacheCount !== 1 ? "es" : ""} avoided (TTL cache)`);
|
|
665
|
+
}
|
|
666
|
+
// ── Per-tool breakdown ──
|
|
667
|
+
const activatedTools = report.savings.by_tool.filter((t) => t.calls > 0);
|
|
668
|
+
if (activatedTools.length > 0) {
|
|
669
|
+
lines.push("", "### Per-tool breakdown", "", "| Tool | Calls | Data processed | Context used | Saved |", "|------|------:|---------------:|-------------:|------:|");
|
|
670
|
+
for (const t of activatedTools) {
|
|
671
|
+
const processed = t.tokens * 4; // bytes approximation from tokens
|
|
672
|
+
const contextUsed = t.context_kb * 1024;
|
|
673
|
+
const savedPct = processed > 0
|
|
674
|
+
? (((processed - contextUsed) / processed) *
|
|
675
|
+
100).toFixed(0)
|
|
676
|
+
: "--";
|
|
677
|
+
lines.push(`| ${t.tool} | ${t.calls} | ${kb(processed)} | ${kb(contextUsed)} | ${savedPct}% |`);
|
|
649
678
|
}
|
|
650
679
|
}
|
|
651
|
-
// ── Session
|
|
680
|
+
// ── Session continuity ──
|
|
652
681
|
if (report.continuity.total_events > 0) {
|
|
653
|
-
lines.push("", "### Session
|
|
654
|
-
|
|
655
|
-
|
|
682
|
+
lines.push("", "### Session continuity", "");
|
|
683
|
+
const parts = [];
|
|
684
|
+
if (report.continuity.compact_count > 0) {
|
|
685
|
+
parts.push(`${report.continuity.compact_count} compaction${report.continuity.compact_count !== 1 ? "s" : ""}`);
|
|
686
|
+
}
|
|
687
|
+
parts.push(`${report.continuity.total_events} event${report.continuity.total_events !== 1 ? "s" : ""} preserved`);
|
|
688
|
+
// Count tasks from continuity categories
|
|
689
|
+
const taskRow = report.continuity.by_category.find((c) => c.category === "task" || c.category === "tasks");
|
|
690
|
+
if (taskRow && taskRow.count > 0) {
|
|
691
|
+
parts.push(`${taskRow.count} task${taskRow.count !== 1 ? "s" : ""} tracked`);
|
|
656
692
|
}
|
|
657
|
-
lines.push(
|
|
658
|
-
lines.push("");
|
|
693
|
+
lines.push(parts.join(" | "));
|
|
659
694
|
if (report.continuity.compact_count > 0) {
|
|
660
|
-
lines.push(`
|
|
695
|
+
lines.push("", `Session knowledge preserved across ${report.continuity.compact_count} compaction${report.continuity.compact_count !== 1 ? "s" : ""} -- zero context lost.`);
|
|
661
696
|
}
|
|
662
697
|
else {
|
|
663
|
-
lines.push(
|
|
698
|
+
lines.push("", "When your context compacts, all of this will restore awareness -- no starting from scratch.");
|
|
664
699
|
}
|
|
665
700
|
if (report.continuity.resume_ready) {
|
|
666
|
-
lines.push(
|
|
701
|
+
lines.push("Resume snapshot ready for the next compaction.");
|
|
667
702
|
}
|
|
668
|
-
lines.push("");
|
|
669
|
-
lines.push(`> **Note:** Previous session data is loaded when you start a new session. Without \`--continue\`, old session history is cleaned up to keep the database lean.`);
|
|
670
703
|
}
|
|
671
|
-
// ── Analytics JSON ──
|
|
704
|
+
// ── Analytics JSON (for power users) ──
|
|
672
705
|
const analyticsJson = {
|
|
673
706
|
session: {
|
|
674
707
|
duration_min: report.session.duration_min,
|
|
@@ -702,7 +735,7 @@ export function formatReport(report) {
|
|
|
702
735
|
continuity: { total_events: report.continuity.total_events },
|
|
703
736
|
};
|
|
704
737
|
lines.push("", "### Analytics (27 metrics)", "", "```json", JSON.stringify(analyticsJson, null, 2), "```");
|
|
705
|
-
//
|
|
706
|
-
lines.push("", "---", "_Display this entire report as-is in your response. Do NOT summarize, collapse, or paraphrase any section._");
|
|
738
|
+
// Footer
|
|
739
|
+
lines.push("", "---", "context-mode -- github.com/mksglu/context-mode", "", "_Display this entire report as-is in your response. Do NOT summarize, collapse, or paraphrase any section._");
|
|
707
740
|
return lines.join("\n");
|
|
708
741
|
}
|