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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code plugins by Mert Koseoğlu",
9
- "version": "1.0.67"
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.67",
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.67",
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.67",
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.67",
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",
@@ -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(0);
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, truncated to 300 chars max */
14
+ /** Extracted payload full data, no truncation */
15
15
  data: string;
16
16
  /** 1=critical (rules, files, tasks) … 5=low */
17
17
  priority: number;
@@ -5,20 +5,17 @@
5
5
  * All 13 event categories as specified in PRD Section 3.
6
6
  */
7
7
  // ── Internal helpers ───────────────────────────────────────────────────────
8
- /** Truncate a string to at most `max` characters. */
9
- function truncate(value, max = 300) {
8
+ /** Null-safe string coercion no truncation, preserves full data. */
9
+ function safeString(value) {
10
10
  if (value == null)
11
11
  return "";
12
- if (value.length <= max)
13
- return value;
14
- return value.slice(0, max);
12
+ return String(value);
15
13
  }
16
- /** Serialise an unknown value to a string, then truncate. */
17
- function truncateAny(value, max = 300) {
14
+ /** Serialise an unknown value to a string no truncation. */
15
+ function safeStringAny(value) {
18
16
  if (value == null)
19
17
  return "";
20
- const str = typeof value === "string" ? value : JSON.stringify(value);
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: truncate(filePath),
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: truncate(tool_response, 5000),
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: truncate(filePath),
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: truncate(filePath),
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: truncate(notebookPath),
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: truncate(filePath),
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: truncate(pattern),
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: truncate(`${searchPattern} in ${searchPath}`),
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: truncate(dir),
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: truncate(response, 300),
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: truncate(match.operation),
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: truncate(JSON.stringify(input.tool_input), 300),
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: ${truncateAny(prompts.map((p) => {
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(", "), 200)})`
244
+ }).join(", "))})`
248
245
  : "exited plan mode";
249
246
  events.push({
250
247
  type: "plan_exit",
251
248
  category: "plan",
252
- data: truncate(detail),
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: truncate(`plan rejected: ${input.tool_response ?? ""}`, 300),
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: truncate(`plan file: ${filePath.split(/[/\\]/).pop() ?? filePath}`),
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: truncate(sanitized),
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: truncate(skillName),
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 = truncate(String(input.tool_input["prompt"] ?? input.tool_input["description"] ?? ""), 200);
354
- const response = input.tool_response ? truncate(String(input.tool_response), 300) : "";
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
- ? truncate(`[completed] ${prompt} → ${response}`, 300)
361
- : truncate(`[launched] ${prompt}`, 300),
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 ? `: ${truncate(String(firstArg), 100)}` : "";
375
+ const argStr = firstArg ? `: ${safeString(String(firstArg))}` : "";
379
376
  return [{
380
377
  type: "mcp",
381
378
  category: "mcp",
382
- data: truncate(`${toolShort}${argStr}`),
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 = truncate(String(input.tool_response ?? ""), 150);
394
+ const answer = safeString(String(input.tool_response ?? ""));
398
395
  const summary = questionText
399
- ? `Q: ${truncate(questionText, 120)} → A: ${answer}`
396
+ ? `Q: ${safeString(questionText)} → A: ${answer}`
400
397
  : `answer: ${answer}`;
401
398
  return [{
402
399
  type: "decision_question",
403
400
  category: "decision",
404
- data: truncate(summary),
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: truncate(`entered worktree: ${name}`),
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: truncate(message, 300),
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: truncate(message, 300),
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: truncate(match.mode),
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: truncate(message, 200),
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 an XML resume snapshot.
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
- * Budget: default 2048 bytes, allocated by priority tier:
9
- * P1 (file, task, rule): 50% = ~1024 bytes
10
- * P2 (cwd, error, decision, env, git): 35% = ~716 bytes
11
- * P3-P4 (subagent, skill, role, data, intent): 15% = ~308 bytes
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] lowest taskId from updates.
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
- * Render <rules> from rule events.
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. Render each section
76
- * 3. Assemble by priority tier with budget trimming
77
- * 4. If over maxBytes, drop lowest priority sections first
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;