context-mode 1.0.67 → 1.0.69
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 +1 -1
- package/build/session/extract.d.ts +1 -1
- package/build/session/extract.js +39 -42
- package/build/session/snapshot.d.ts +15 -48
- package/build/session/snapshot.js +267 -208
- package/build/truncate.d.ts +0 -10
- package/build/truncate.js +0 -17
- package/hooks/codex/sessionstart.mjs +5 -13
- package/hooks/cursor/sessionstart.mjs +5 -13
- package/hooks/gemini-cli/sessionstart.mjs +6 -14
- package/hooks/session-directive.mjs +3 -2
- package/hooks/session-extract.bundle.mjs +1 -1
- package/hooks/session-snapshot.bundle.mjs +29 -14
- package/hooks/sessionstart.mjs +6 -18
- package/hooks/vscode-copilot/sessionstart.mjs +6 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -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.69"
|
|
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.69",
|
|
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.69",
|
|
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.69",
|
|
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.69",
|
|
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
|
@@ -44,7 +44,7 @@ export const ContextModePlugin = async (ctx) => {
|
|
|
44
44
|
const sessionId = randomUUID();
|
|
45
45
|
db.ensureSession(sessionId, projectDir);
|
|
46
46
|
// Clean up old sessions on startup (replaces SessionStart hook)
|
|
47
|
-
db.cleanupOldSessions(
|
|
47
|
+
db.cleanupOldSessions(7);
|
|
48
48
|
return {
|
|
49
49
|
// ── PreToolUse: Routing enforcement ─────────────────
|
|
50
50
|
"tool.execute.before": async (input, output) => {
|
|
@@ -11,7 +11,7 @@ export interface SessionEvent {
|
|
|
11
11
|
/** e.g. "file", "cwd", "error", "git", "task", "decision",
|
|
12
12
|
* "rule", "env", "role", "skill", "subagent", "data", "intent" */
|
|
13
13
|
category: string;
|
|
14
|
-
/** Extracted payload
|
|
14
|
+
/** Extracted payload — full data, no truncation */
|
|
15
15
|
data: string;
|
|
16
16
|
/** 1=critical (rules, files, tasks) … 5=low */
|
|
17
17
|
priority: number;
|
package/build/session/extract.js
CHANGED
|
@@ -5,20 +5,17 @@
|
|
|
5
5
|
* All 13 event categories as specified in PRD Section 3.
|
|
6
6
|
*/
|
|
7
7
|
// ── Internal helpers ───────────────────────────────────────────────────────
|
|
8
|
-
/**
|
|
9
|
-
function
|
|
8
|
+
/** Null-safe string coercion — no truncation, preserves full data. */
|
|
9
|
+
function safeString(value) {
|
|
10
10
|
if (value == null)
|
|
11
11
|
return "";
|
|
12
|
-
|
|
13
|
-
return value;
|
|
14
|
-
return value.slice(0, max);
|
|
12
|
+
return String(value);
|
|
15
13
|
}
|
|
16
|
-
/** Serialise an unknown value to a string
|
|
17
|
-
function
|
|
14
|
+
/** Serialise an unknown value to a string — no truncation. */
|
|
15
|
+
function safeStringAny(value) {
|
|
18
16
|
if (value == null)
|
|
19
17
|
return "";
|
|
20
|
-
|
|
21
|
-
return truncate(str, max);
|
|
18
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
22
19
|
}
|
|
23
20
|
// ── Category extractors ────────────────────────────────────────────────────
|
|
24
21
|
/**
|
|
@@ -41,7 +38,7 @@ function extractFileAndRule(input) {
|
|
|
41
38
|
events.push({
|
|
42
39
|
type: "rule",
|
|
43
40
|
category: "rule",
|
|
44
|
-
data:
|
|
41
|
+
data: safeString(filePath),
|
|
45
42
|
priority: 1,
|
|
46
43
|
});
|
|
47
44
|
// Capture rule content so it survives context compaction
|
|
@@ -49,7 +46,7 @@ function extractFileAndRule(input) {
|
|
|
49
46
|
events.push({
|
|
50
47
|
type: "rule_content",
|
|
51
48
|
category: "rule",
|
|
52
|
-
data:
|
|
49
|
+
data: safeString(tool_response),
|
|
53
50
|
priority: 1,
|
|
54
51
|
});
|
|
55
52
|
}
|
|
@@ -58,7 +55,7 @@ function extractFileAndRule(input) {
|
|
|
58
55
|
events.push({
|
|
59
56
|
type: "file_read",
|
|
60
57
|
category: "file",
|
|
61
|
-
data:
|
|
58
|
+
data: safeString(filePath),
|
|
62
59
|
priority: 1,
|
|
63
60
|
});
|
|
64
61
|
return events;
|
|
@@ -68,7 +65,7 @@ function extractFileAndRule(input) {
|
|
|
68
65
|
events.push({
|
|
69
66
|
type: "file_edit",
|
|
70
67
|
category: "file",
|
|
71
|
-
data:
|
|
68
|
+
data: safeString(filePath),
|
|
72
69
|
priority: 1,
|
|
73
70
|
});
|
|
74
71
|
return events;
|
|
@@ -78,7 +75,7 @@ function extractFileAndRule(input) {
|
|
|
78
75
|
events.push({
|
|
79
76
|
type: "file_edit",
|
|
80
77
|
category: "file",
|
|
81
|
-
data:
|
|
78
|
+
data: safeString(notebookPath),
|
|
82
79
|
priority: 1,
|
|
83
80
|
});
|
|
84
81
|
return events;
|
|
@@ -88,7 +85,7 @@ function extractFileAndRule(input) {
|
|
|
88
85
|
events.push({
|
|
89
86
|
type: "file_write",
|
|
90
87
|
category: "file",
|
|
91
|
-
data:
|
|
88
|
+
data: safeString(filePath),
|
|
92
89
|
priority: 1,
|
|
93
90
|
});
|
|
94
91
|
return events;
|
|
@@ -99,7 +96,7 @@ function extractFileAndRule(input) {
|
|
|
99
96
|
events.push({
|
|
100
97
|
type: "file_glob",
|
|
101
98
|
category: "file",
|
|
102
|
-
data:
|
|
99
|
+
data: safeString(pattern),
|
|
103
100
|
priority: 3,
|
|
104
101
|
});
|
|
105
102
|
return events;
|
|
@@ -111,7 +108,7 @@ function extractFileAndRule(input) {
|
|
|
111
108
|
events.push({
|
|
112
109
|
type: "file_search",
|
|
113
110
|
category: "file",
|
|
114
|
-
data:
|
|
111
|
+
data: safeString(`${searchPattern} in ${searchPath}`),
|
|
115
112
|
priority: 3,
|
|
116
113
|
});
|
|
117
114
|
return events;
|
|
@@ -134,7 +131,7 @@ function extractCwd(input) {
|
|
|
134
131
|
return [{
|
|
135
132
|
type: "cwd",
|
|
136
133
|
category: "cwd",
|
|
137
|
-
data:
|
|
134
|
+
data: safeString(dir),
|
|
138
135
|
priority: 2,
|
|
139
136
|
}];
|
|
140
137
|
}
|
|
@@ -154,7 +151,7 @@ function extractError(input) {
|
|
|
154
151
|
return [{
|
|
155
152
|
type: "error_tool",
|
|
156
153
|
category: "error",
|
|
157
|
-
data:
|
|
154
|
+
data: safeString(response),
|
|
158
155
|
priority: 2,
|
|
159
156
|
}];
|
|
160
157
|
}
|
|
@@ -192,7 +189,7 @@ function extractGit(input) {
|
|
|
192
189
|
return [{
|
|
193
190
|
type: "git",
|
|
194
191
|
category: "git",
|
|
195
|
-
data:
|
|
192
|
+
data: safeString(match.operation),
|
|
196
193
|
priority: 2,
|
|
197
194
|
}];
|
|
198
195
|
}
|
|
@@ -211,7 +208,7 @@ function extractTask(input) {
|
|
|
211
208
|
return [{
|
|
212
209
|
type,
|
|
213
210
|
category: "task",
|
|
214
|
-
data:
|
|
211
|
+
data: safeString(JSON.stringify(input.tool_input)),
|
|
215
212
|
priority: 1,
|
|
216
213
|
}];
|
|
217
214
|
}
|
|
@@ -240,16 +237,16 @@ function extractPlan(input) {
|
|
|
240
237
|
// Plan exit event with allowedPrompts detail
|
|
241
238
|
const prompts = input.tool_input["allowedPrompts"];
|
|
242
239
|
const detail = Array.isArray(prompts) && prompts.length > 0
|
|
243
|
-
? `exited plan mode (allowed: ${
|
|
240
|
+
? `exited plan mode (allowed: ${safeStringAny(prompts.map((p) => {
|
|
244
241
|
if (typeof p === "object" && p !== null && "prompt" in p)
|
|
245
242
|
return String(p.prompt);
|
|
246
243
|
return String(p);
|
|
247
|
-
}).join(", ")
|
|
244
|
+
}).join(", "))})`
|
|
248
245
|
: "exited plan mode";
|
|
249
246
|
events.push({
|
|
250
247
|
type: "plan_exit",
|
|
251
248
|
category: "plan",
|
|
252
|
-
data:
|
|
249
|
+
data: safeString(detail),
|
|
253
250
|
priority: 2,
|
|
254
251
|
});
|
|
255
252
|
// Detect approval/rejection from tool_response
|
|
@@ -266,7 +263,7 @@ function extractPlan(input) {
|
|
|
266
263
|
events.push({
|
|
267
264
|
type: "plan_rejected",
|
|
268
265
|
category: "plan",
|
|
269
|
-
data:
|
|
266
|
+
data: safeString(`plan rejected: ${input.tool_response ?? ""}`),
|
|
270
267
|
priority: 2,
|
|
271
268
|
});
|
|
272
269
|
}
|
|
@@ -279,7 +276,7 @@ function extractPlan(input) {
|
|
|
279
276
|
return [{
|
|
280
277
|
type: "plan_file_write",
|
|
281
278
|
category: "plan",
|
|
282
|
-
data:
|
|
279
|
+
data: safeString(`plan file: ${filePath.split(/[/\\]/).pop() ?? filePath}`),
|
|
283
280
|
priority: 2,
|
|
284
281
|
}];
|
|
285
282
|
}
|
|
@@ -322,7 +319,7 @@ function extractEnv(input) {
|
|
|
322
319
|
return [{
|
|
323
320
|
type: "env",
|
|
324
321
|
category: "env",
|
|
325
|
-
data:
|
|
322
|
+
data: safeString(sanitized),
|
|
326
323
|
priority: 2,
|
|
327
324
|
}];
|
|
328
325
|
}
|
|
@@ -337,7 +334,7 @@ function extractSkill(input) {
|
|
|
337
334
|
return [{
|
|
338
335
|
type: "skill",
|
|
339
336
|
category: "skill",
|
|
340
|
-
data:
|
|
337
|
+
data: safeString(skillName),
|
|
341
338
|
priority: 3,
|
|
342
339
|
}];
|
|
343
340
|
}
|
|
@@ -350,15 +347,15 @@ function extractSkill(input) {
|
|
|
350
347
|
function extractSubagent(input) {
|
|
351
348
|
if (input.tool_name !== "Agent")
|
|
352
349
|
return [];
|
|
353
|
-
const prompt =
|
|
354
|
-
const response = input.tool_response ?
|
|
350
|
+
const prompt = safeString(String(input.tool_input["prompt"] ?? input.tool_input["description"] ?? ""));
|
|
351
|
+
const response = input.tool_response ? safeString(String(input.tool_response)) : "";
|
|
355
352
|
const isCompleted = response.length > 0;
|
|
356
353
|
return [{
|
|
357
354
|
type: isCompleted ? "subagent_completed" : "subagent_launched",
|
|
358
355
|
category: "subagent",
|
|
359
356
|
data: isCompleted
|
|
360
|
-
?
|
|
361
|
-
:
|
|
357
|
+
? safeString(`[completed] ${prompt} → ${response}`)
|
|
358
|
+
: safeString(`[launched] ${prompt}`),
|
|
362
359
|
priority: isCompleted ? 2 : 3,
|
|
363
360
|
}];
|
|
364
361
|
}
|
|
@@ -375,11 +372,11 @@ function extractMcp(input) {
|
|
|
375
372
|
const toolShort = parts[parts.length - 1] || tool_name;
|
|
376
373
|
// Extract first string argument for context
|
|
377
374
|
const firstArg = Object.values(tool_input).find((v) => typeof v === "string");
|
|
378
|
-
const argStr = firstArg ? `: ${
|
|
375
|
+
const argStr = firstArg ? `: ${safeString(String(firstArg))}` : "";
|
|
379
376
|
return [{
|
|
380
377
|
type: "mcp",
|
|
381
378
|
category: "mcp",
|
|
382
|
-
data:
|
|
379
|
+
data: safeString(`${toolShort}${argStr}`),
|
|
383
380
|
priority: 3,
|
|
384
381
|
}];
|
|
385
382
|
}
|
|
@@ -394,14 +391,14 @@ function extractDecision(input) {
|
|
|
394
391
|
const questionText = Array.isArray(questions) && questions.length > 0
|
|
395
392
|
? String(questions[0]["question"] ?? "")
|
|
396
393
|
: "";
|
|
397
|
-
const answer =
|
|
394
|
+
const answer = safeString(String(input.tool_response ?? ""));
|
|
398
395
|
const summary = questionText
|
|
399
|
-
? `Q: ${
|
|
396
|
+
? `Q: ${safeString(questionText)} → A: ${answer}`
|
|
400
397
|
: `answer: ${answer}`;
|
|
401
398
|
return [{
|
|
402
399
|
type: "decision_question",
|
|
403
400
|
category: "decision",
|
|
404
|
-
data:
|
|
401
|
+
data: safeString(summary),
|
|
405
402
|
priority: 2,
|
|
406
403
|
}];
|
|
407
404
|
}
|
|
@@ -416,7 +413,7 @@ function extractWorktree(input) {
|
|
|
416
413
|
return [{
|
|
417
414
|
type: "worktree",
|
|
418
415
|
category: "env",
|
|
419
|
-
data:
|
|
416
|
+
data: safeString(`entered worktree: ${name}`),
|
|
420
417
|
priority: 2,
|
|
421
418
|
}];
|
|
422
419
|
}
|
|
@@ -439,7 +436,7 @@ function extractUserDecision(message) {
|
|
|
439
436
|
return [{
|
|
440
437
|
type: "decision",
|
|
441
438
|
category: "decision",
|
|
442
|
-
data:
|
|
439
|
+
data: safeString(message),
|
|
443
440
|
priority: 2,
|
|
444
441
|
}];
|
|
445
442
|
}
|
|
@@ -460,7 +457,7 @@ function extractRole(message) {
|
|
|
460
457
|
return [{
|
|
461
458
|
type: "role",
|
|
462
459
|
category: "role",
|
|
463
|
-
data:
|
|
460
|
+
data: safeString(message),
|
|
464
461
|
priority: 3,
|
|
465
462
|
}];
|
|
466
463
|
}
|
|
@@ -481,7 +478,7 @@ function extractIntent(message) {
|
|
|
481
478
|
return [{
|
|
482
479
|
type: "intent",
|
|
483
480
|
category: "intent",
|
|
484
|
-
data:
|
|
481
|
+
data: safeString(match.mode),
|
|
485
482
|
priority: 4,
|
|
486
483
|
}];
|
|
487
484
|
}
|
|
@@ -495,7 +492,7 @@ function extractData(message) {
|
|
|
495
492
|
return [{
|
|
496
493
|
type: "data",
|
|
497
494
|
category: "data",
|
|
498
|
-
data:
|
|
495
|
+
data: safeString(message),
|
|
499
496
|
priority: 4,
|
|
500
497
|
}];
|
|
501
498
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Snapshot builder — converts stored SessionEvents into
|
|
2
|
+
* Snapshot builder — converts stored SessionEvents into a reference-based
|
|
3
|
+
* XML resume snapshot.
|
|
3
4
|
*
|
|
4
5
|
* Pure functions only. No database access, no file system, no side effects.
|
|
5
|
-
* The output XML is injected into Claude's context after a compact event to
|
|
6
|
-
* restore session awareness.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* The output XML is injected into the LLM's context after a compact event to
|
|
8
|
+
* restore session awareness. Instead of truncated inline data, each section
|
|
9
|
+
* contains a natural summary plus a runnable search tool call that retrieves
|
|
10
|
+
* full details from the indexed knowledge base on demand.
|
|
11
|
+
*
|
|
12
|
+
* Zero truncation. Zero information loss. Full data lives in SessionDB;
|
|
13
|
+
* the snapshot is a table of contents.
|
|
12
14
|
*/
|
|
13
15
|
/** Stored event as read from SessionDB. */
|
|
14
16
|
export interface StoredEvent {
|
|
@@ -21,59 +23,24 @@ export interface StoredEvent {
|
|
|
21
23
|
export interface BuildSnapshotOpts {
|
|
22
24
|
maxBytes?: number;
|
|
23
25
|
compactCount?: number;
|
|
26
|
+
searchTool?: string;
|
|
24
27
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Render <active_files> from file events.
|
|
27
|
-
* Deduplicates by path, counts operations, keeps the last 10 files.
|
|
28
|
-
*/
|
|
29
|
-
export declare function renderActiveFiles(fileEvents: StoredEvent[]): string;
|
|
30
28
|
/**
|
|
31
29
|
* Render <task_state> from task events.
|
|
32
30
|
* Reconstructs the full task list from create/update events,
|
|
33
31
|
* filters out completed tasks, and renders only pending/in-progress work.
|
|
34
32
|
*
|
|
35
33
|
* TaskCreate events have `{ subject }`, TaskUpdate events have `{ taskId, status }`.
|
|
36
|
-
* Match by chronological order: creates[0]
|
|
34
|
+
* Match by chronological order: creates[0] -> lowest taskId from updates.
|
|
37
35
|
*/
|
|
38
36
|
export declare function renderTaskState(taskEvents: StoredEvent[]): string;
|
|
39
37
|
/**
|
|
40
|
-
*
|
|
41
|
-
* Lists each unique rule source path + content summaries.
|
|
42
|
-
*/
|
|
43
|
-
export declare function renderRules(ruleEvents: StoredEvent[]): string;
|
|
44
|
-
/**
|
|
45
|
-
* Render <decisions> from decision events.
|
|
46
|
-
*/
|
|
47
|
-
export declare function renderDecisions(decisionEvents: StoredEvent[]): string;
|
|
48
|
-
/**
|
|
49
|
-
* Render <environment> from cwd, env, and git events.
|
|
50
|
-
*/
|
|
51
|
-
export declare function renderEnvironment(cwdEvent: StoredEvent | undefined, envEvents: StoredEvent[], gitEvent: StoredEvent | undefined): string;
|
|
52
|
-
/**
|
|
53
|
-
* Render <errors_encountered> from error events.
|
|
54
|
-
*/
|
|
55
|
-
export declare function renderErrors(errorEvents: StoredEvent[]): string;
|
|
56
|
-
/**
|
|
57
|
-
* Render <intent> from the most recent intent event.
|
|
58
|
-
*/
|
|
59
|
-
export declare function renderIntent(intentEvent: StoredEvent): string;
|
|
60
|
-
/**
|
|
61
|
-
* Render <subagents> from subagent events.
|
|
62
|
-
* Shows agent dispatch status (launched/completed) and result summaries.
|
|
63
|
-
*/
|
|
64
|
-
export declare function renderSubagents(subagentEvents: StoredEvent[]): string;
|
|
65
|
-
/**
|
|
66
|
-
* Render <mcp_tools> from MCP tool call events.
|
|
67
|
-
* Deduplicates by tool name, shows usage count.
|
|
68
|
-
*/
|
|
69
|
-
export declare function renderMcpTools(mcpEvents: StoredEvent[]): string;
|
|
70
|
-
/**
|
|
71
|
-
* Build a resume snapshot XML string from stored session events.
|
|
38
|
+
* Build a reference-based resume snapshot XML string from stored session events.
|
|
72
39
|
*
|
|
73
40
|
* Algorithm:
|
|
74
41
|
* 1. Group events by category
|
|
75
|
-
* 2.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
42
|
+
* 2. For each non-empty category, build a summary section with a runnable
|
|
43
|
+
* search tool call containing exact queries for full details
|
|
44
|
+
* 3. Assemble ALL non-empty sections — no priority dropping, no byte budget
|
|
78
45
|
*/
|
|
79
46
|
export declare function buildResumeSnapshot(events: StoredEvent[], opts?: BuildSnapshotOpts): string;
|