context-mode 1.0.105 → 1.0.106
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/opencode-plugin.js +31 -4
- package/build/server.js +9 -1
- package/build/session/db.d.ts +12 -3
- package/build/session/db.js +19 -4
- package/cli.bundle.mjs +84 -83
- package/hooks/session-db.bundle.mjs +4 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +57 -56
|
@@ -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.106"
|
|
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.106",
|
|
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.106",
|
|
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.106",
|
|
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.106",
|
|
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/opencode-plugin.js
CHANGED
|
@@ -20,11 +20,27 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { dirname, resolve } from "node:path";
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
24
|
import { SessionDB } from "./session/db.js";
|
|
24
25
|
import { extractEvents } from "./session/extract.js";
|
|
25
26
|
import { buildResumeSnapshot } from "./session/snapshot.js";
|
|
26
27
|
import { OpenCodeAdapter } from "./adapters/opencode/index.js";
|
|
27
28
|
import { PLATFORM_ENV_VARS } from "./adapters/detect.js";
|
|
29
|
+
// Read package.json version once at module load (not on every hook call).
|
|
30
|
+
// Used in the resume-injection visible signal so users can confirm in
|
|
31
|
+
// OPENCODE_DEBUG logs which plugin version actually injected.
|
|
32
|
+
const VERSION = (() => {
|
|
33
|
+
try {
|
|
34
|
+
const pkgRoot = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
for (const rel of ["../package.json", "./package.json"]) {
|
|
36
|
+
const p = resolve(pkgRoot, rel);
|
|
37
|
+
if (existsSync(p))
|
|
38
|
+
return JSON.parse(readFileSync(p, "utf8")).version ?? "unknown";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch { /* fall through */ }
|
|
42
|
+
return "unknown";
|
|
43
|
+
})();
|
|
28
44
|
// ── Helpers ───────────────────────────────────────────────
|
|
29
45
|
/**
|
|
30
46
|
* Detect whether the plugin is running under KiloCode or OpenCode.
|
|
@@ -166,12 +182,21 @@ async function createContextModePlugin(ctx) {
|
|
|
166
182
|
return;
|
|
167
183
|
if (resumeInjected.has(sessionId))
|
|
168
184
|
return;
|
|
169
|
-
resumeInjected.add(sessionId);
|
|
170
185
|
try {
|
|
171
|
-
|
|
186
|
+
// Pass current sessionId so SQL excludes self-injection (v1.0.106 — Mickey #376
|
|
187
|
+
// follow-up): if Session B compacts mid-flight and produces its own row,
|
|
188
|
+
// B's next system.transform must NOT claim that row back into B's prompt.
|
|
189
|
+
const row = db.claimLatestUnconsumedResume(sessionId);
|
|
172
190
|
if (!row || !row.snapshot)
|
|
173
|
-
return;
|
|
191
|
+
return; // no row → leave `resumeInjected` unset → retry on next turn
|
|
174
192
|
if (Array.isArray(output?.system)) {
|
|
193
|
+
// Visible signal — without this, the injection is silent and users
|
|
194
|
+
// cannot tell the feature is active (Mickey: "I can't find use case
|
|
195
|
+
// for it"). The XML comment is harmless to the model and shows up in
|
|
196
|
+
// OPENCODE_DEBUG logs as proof the snapshot landed.
|
|
197
|
+
const eventCount = row.snapshot.match(/events="(\d+)"/)?.[1] ?? "?";
|
|
198
|
+
const marker = `<!-- context-mode v${VERSION}: resumed prior session ${row.sessionId.slice(0, 8)} ` +
|
|
199
|
+
`(${eventCount} events, ${row.snapshot.length} chars) -->\n`;
|
|
175
200
|
// Insert at index 1 (after the header) — NOT unshift.
|
|
176
201
|
// OpenCode's llm.ts:117-128 saves `header = system[0]` BEFORE this
|
|
177
202
|
// hook runs and then folds the rest into a 2-part structure
|
|
@@ -182,7 +207,9 @@ async function createContextModePlugin(ctx) {
|
|
|
182
207
|
// provider prompt cache is invalidated on every resume injection.
|
|
183
208
|
// Inserting at index 1 keeps the header invariant and lets the
|
|
184
209
|
// snapshot ride along inside the cached body block.
|
|
185
|
-
output.system.splice(1, 0, row.snapshot);
|
|
210
|
+
output.system.splice(1, 0, marker + row.snapshot);
|
|
211
|
+
// Mark consumed only AFTER successful splice so failed paths can retry
|
|
212
|
+
resumeInjected.add(sessionId);
|
|
186
213
|
}
|
|
187
214
|
}
|
|
188
215
|
catch {
|
package/build/server.js
CHANGED
|
@@ -2805,9 +2805,17 @@ async function main() {
|
|
|
2805
2805
|
}
|
|
2806
2806
|
}
|
|
2807
2807
|
catch { /* best effort — _detectedAdapter stays null, falls back to .claude */ }
|
|
2808
|
-
// Non-blocking version check — result stored for trackResponse warnings
|
|
2808
|
+
// Non-blocking version check — result stored for trackResponse warnings.
|
|
2809
|
+
// First fetch at startup, then refresh every hour so long-running sessions
|
|
2810
|
+
// (some users keep the MCP server alive 24h+) catch new releases without a
|
|
2811
|
+
// restart. `.unref()` lets the process exit normally on SIGTERM regardless
|
|
2812
|
+
// of pending intervals.
|
|
2809
2813
|
fetchLatestVersion().then(v => { if (v !== "unknown")
|
|
2810
2814
|
_latestVersion = v; });
|
|
2815
|
+
setInterval(() => {
|
|
2816
|
+
fetchLatestVersion().then(v => { if (v !== "unknown")
|
|
2817
|
+
_latestVersion = v; });
|
|
2818
|
+
}, 60 * 60 * 1000).unref();
|
|
2811
2819
|
console.error(`Context Mode MCP server v${VERSION} running on stdio`);
|
|
2812
2820
|
console.error(`Detected runtimes:\n${getRuntimeSummary(runtimes)}`);
|
|
2813
2821
|
if (!hasBunRuntime()) {
|
package/build/session/db.d.ts
CHANGED
|
@@ -149,16 +149,25 @@ export declare class SessionDB extends SQLiteBase {
|
|
|
149
149
|
*/
|
|
150
150
|
markResumeConsumed(sessionId: string): void;
|
|
151
151
|
/**
|
|
152
|
-
* Atomically claim the most recent unconsumed resume snapshot in this DB
|
|
152
|
+
* Atomically claim the most recent unconsumed resume snapshot in this DB,
|
|
153
|
+
* EXCLUDING any row that belongs to `currentSessionId`.
|
|
153
154
|
*
|
|
154
155
|
* `SessionDB` is sharded per project (see `getSessionDBPath` — SHA-256 of
|
|
155
156
|
* project dir), so "this DB" already implies "this project". The atomic
|
|
156
157
|
* `UPDATE … RETURNING` ensures concurrent processes for the same project
|
|
157
158
|
* cannot both inject the same snapshot (Mickey / PR #376 race).
|
|
158
159
|
*
|
|
159
|
-
*
|
|
160
|
+
* The `currentSessionId` parameter prevents self-injection: when a session
|
|
161
|
+
* compacts mid-flight and produces its own row, that session's next chat
|
|
162
|
+
* turn must NOT claim that row back (wasted tokens AND it would consume
|
|
163
|
+
* the snapshot meant for the next fresh session).
|
|
164
|
+
*
|
|
165
|
+
* Pass an empty string to allow self-claim (legacy behaviour, only useful
|
|
166
|
+
* in tests or one-off harnesses).
|
|
167
|
+
*
|
|
168
|
+
* Returns null when no unconsumed snapshot exists for any other session.
|
|
160
169
|
*/
|
|
161
|
-
claimLatestUnconsumedResume(): {
|
|
170
|
+
claimLatestUnconsumedResume(currentSessionId: string): {
|
|
162
171
|
sessionId: string;
|
|
163
172
|
snapshot: string;
|
|
164
173
|
} | null;
|
package/build/session/db.js
CHANGED
|
@@ -256,11 +256,17 @@ export class SessionDB extends SQLiteBase {
|
|
|
256
256
|
// statement". Required for race-safe cross-session resume injection
|
|
257
257
|
// (Mickey / PR #376) — two parallel chat-turn hooks must not both read
|
|
258
258
|
// the same row before either one writes consumed=1.
|
|
259
|
+
//
|
|
260
|
+
// The `session_id != ?` clause prevents self-injection (v1.0.106): when
|
|
261
|
+
// Session B compacts mid-flight and produces its own row, B's next chat
|
|
262
|
+
// turn must NOT claim that row back into its own prompt — that's wasted
|
|
263
|
+
// tokens and steals the snapshot meant for the next fresh session.
|
|
259
264
|
p(S.claimLatestUnconsumedResume, `UPDATE session_resume
|
|
260
265
|
SET consumed = 1
|
|
261
266
|
WHERE id = (
|
|
262
267
|
SELECT id FROM session_resume
|
|
263
268
|
WHERE consumed = 0
|
|
269
|
+
AND session_id != ?
|
|
264
270
|
ORDER BY created_at DESC, id DESC
|
|
265
271
|
LIMIT 1
|
|
266
272
|
)
|
|
@@ -493,17 +499,26 @@ export class SessionDB extends SQLiteBase {
|
|
|
493
499
|
this.stmt(S.markResumeConsumed).run(sessionId);
|
|
494
500
|
}
|
|
495
501
|
/**
|
|
496
|
-
* Atomically claim the most recent unconsumed resume snapshot in this DB
|
|
502
|
+
* Atomically claim the most recent unconsumed resume snapshot in this DB,
|
|
503
|
+
* EXCLUDING any row that belongs to `currentSessionId`.
|
|
497
504
|
*
|
|
498
505
|
* `SessionDB` is sharded per project (see `getSessionDBPath` — SHA-256 of
|
|
499
506
|
* project dir), so "this DB" already implies "this project". The atomic
|
|
500
507
|
* `UPDATE … RETURNING` ensures concurrent processes for the same project
|
|
501
508
|
* cannot both inject the same snapshot (Mickey / PR #376 race).
|
|
502
509
|
*
|
|
503
|
-
*
|
|
510
|
+
* The `currentSessionId` parameter prevents self-injection: when a session
|
|
511
|
+
* compacts mid-flight and produces its own row, that session's next chat
|
|
512
|
+
* turn must NOT claim that row back (wasted tokens AND it would consume
|
|
513
|
+
* the snapshot meant for the next fresh session).
|
|
514
|
+
*
|
|
515
|
+
* Pass an empty string to allow self-claim (legacy behaviour, only useful
|
|
516
|
+
* in tests or one-off harnesses).
|
|
517
|
+
*
|
|
518
|
+
* Returns null when no unconsumed snapshot exists for any other session.
|
|
504
519
|
*/
|
|
505
|
-
claimLatestUnconsumedResume() {
|
|
506
|
-
const row = this.stmt(S.claimLatestUnconsumedResume).get();
|
|
520
|
+
claimLatestUnconsumedResume(currentSessionId) {
|
|
521
|
+
const row = this.stmt(S.claimLatestUnconsumedResume).get(currentSessionId);
|
|
507
522
|
if (!row)
|
|
508
523
|
return null;
|
|
509
524
|
return { sessionId: row.session_id, snapshot: row.snapshot };
|