context-mode 1.0.159 → 1.0.160
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/hooks/posttooluse.mjs +44 -17
- package/hooks/precompact.mjs +30 -23
- package/hooks/session-loaders.mjs +14 -6
- package/hooks/sessionstart.mjs +38 -19
- package/hooks/userpromptsubmit.mjs +14 -2
- 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.160"
|
|
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.160",
|
|
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.160",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.160",
|
|
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.160",
|
|
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.160",
|
|
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/hooks/posttooluse.mjs
CHANGED
|
@@ -71,12 +71,23 @@ await runHook(async () => {
|
|
|
71
71
|
const colonIdx = rejectedData.indexOf(":");
|
|
72
72
|
const rejTool = colonIdx > 0 ? rejectedData.slice(0, colonIdx) : rejectedData;
|
|
73
73
|
const rejReason = colonIdx > 0 ? rejectedData.slice(colonIdx + 1) : "denied";
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
// v1.0.160: route through attributeAndInsertEvents so the bridge wire
|
|
75
|
+
// receives this event too. db.insertEvent only writes locally — the
|
|
76
|
+
// dashboard's rejection-rate widget needs the platform row.
|
|
77
|
+
attributeAndInsertEvents(
|
|
78
|
+
db,
|
|
79
|
+
sessionId,
|
|
80
|
+
[{
|
|
81
|
+
type: "rejected",
|
|
82
|
+
category: "rejected-approach",
|
|
83
|
+
data: `${rejTool}: ${rejReason}`,
|
|
84
|
+
priority: 2,
|
|
85
|
+
}],
|
|
86
|
+
input,
|
|
87
|
+
projectDir,
|
|
88
|
+
"PreToolUse",
|
|
89
|
+
resolveProjectAttributions,
|
|
90
|
+
);
|
|
80
91
|
}
|
|
81
92
|
} catch { /* best-effort */ }
|
|
82
93
|
|
|
@@ -108,17 +119,24 @@ await runHook(async () => {
|
|
|
108
119
|
const summary = redirectData.slice(i3 + 1);
|
|
109
120
|
const bytesAvoided = Number.parseInt(bytesRaw, 10);
|
|
110
121
|
if (Number.isFinite(bytesAvoided) && bytesAvoided > 0) {
|
|
111
|
-
|
|
122
|
+
// v1.0.160: route through wire — context-saving (byte-accounting)
|
|
123
|
+
// widget on the platform reads category='redirect' rows. event
|
|
124
|
+
// carries bytes_avoided so the bytesList branch in
|
|
125
|
+
// attributeAndInsertEvents stamps the column.
|
|
126
|
+
attributeAndInsertEvents(
|
|
127
|
+
db,
|
|
112
128
|
sessionId,
|
|
113
|
-
{
|
|
129
|
+
[{
|
|
114
130
|
type,
|
|
115
131
|
category: "redirect",
|
|
116
132
|
data: `${tool}: ${summary}`,
|
|
117
133
|
priority: 2,
|
|
118
|
-
|
|
134
|
+
bytes_avoided: bytesAvoided,
|
|
135
|
+
}],
|
|
136
|
+
input,
|
|
137
|
+
projectDir,
|
|
119
138
|
"PreToolUse",
|
|
120
|
-
|
|
121
|
-
{ bytesAvoided, bytesReturned: 0 },
|
|
139
|
+
resolveProjectAttributions,
|
|
122
140
|
);
|
|
123
141
|
}
|
|
124
142
|
}
|
|
@@ -140,12 +158,21 @@ await runHook(async () => {
|
|
|
140
158
|
if (startTime && !isNaN(startTime)) {
|
|
141
159
|
const duration = Date.now() - startTime;
|
|
142
160
|
if (duration > 5000) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
161
|
+
// v1.0.160: route through wire — slow-tool insights need this row.
|
|
162
|
+
attributeAndInsertEvents(
|
|
163
|
+
db,
|
|
164
|
+
sessionId,
|
|
165
|
+
[{
|
|
166
|
+
type: "tool_latency",
|
|
167
|
+
category: "latency",
|
|
168
|
+
data: `${toolName}: ${duration}ms`,
|
|
169
|
+
priority: 3,
|
|
170
|
+
}],
|
|
171
|
+
input,
|
|
172
|
+
projectDir,
|
|
173
|
+
"PostToolUse",
|
|
174
|
+
resolveProjectAttributions,
|
|
175
|
+
);
|
|
149
176
|
}
|
|
150
177
|
}
|
|
151
178
|
}
|
package/hooks/precompact.mjs
CHANGED
|
@@ -17,16 +17,17 @@ await runHook(async () => {
|
|
|
17
17
|
parseStdin,
|
|
18
18
|
getSessionId,
|
|
19
19
|
getSessionDBPath,
|
|
20
|
+
getInputProjectDir,
|
|
20
21
|
resolveConfigDir,
|
|
21
22
|
} = await import("./session-helpers.mjs");
|
|
22
|
-
const { createSessionLoaders } = await import("./session-loaders.mjs");
|
|
23
|
+
const { createSessionLoaders, attributeAndInsertEvents } = await import("./session-loaders.mjs");
|
|
23
24
|
const { appendFileSync } = await import("node:fs");
|
|
24
25
|
const { join, dirname } = await import("node:path");
|
|
25
26
|
const { fileURLToPath } = await import("node:url");
|
|
26
27
|
|
|
27
28
|
// Resolve absolute path for imports
|
|
28
29
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
29
|
-
const { loadSessionDB, loadSnapshot } = createSessionLoaders(HOOK_DIR);
|
|
30
|
+
const { loadSessionDB, loadSnapshot, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
|
|
30
31
|
const DEBUG_LOG = join(resolveConfigDir(), "context-mode", "precompact-debug.log");
|
|
31
32
|
|
|
32
33
|
try {
|
|
@@ -52,31 +53,37 @@ await runHook(async () => {
|
|
|
52
53
|
db.upsertResume(sessionId, snapshot, events.length);
|
|
53
54
|
db.incrementCompactCount(sessionId);
|
|
54
55
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
type: "compaction_summary",
|
|
59
|
-
category: "compaction",
|
|
60
|
-
data: `Session compacted. ${events.length} events, ${fileEvents.length} files touched.`,
|
|
61
|
-
priority: 1,
|
|
62
|
-
}, "PreCompact");
|
|
63
|
-
|
|
64
|
-
// D2 PRD Phase 6.1: emit snapshot-built event with bytes_avoided=snapshot.length
|
|
65
|
-
// Snapshot bytes are bytes the model would have re-read on resume but didn't.
|
|
56
|
+
// v1.0.160: route compaction lifecycle events through wire so
|
|
57
|
+
// dashboard's compact widget gets per-compaction rows (the engine
|
|
58
|
+
// joins on category='compaction' to compute snapshot insights).
|
|
66
59
|
try {
|
|
67
|
-
|
|
60
|
+
const fileEvents = events.filter(e => e.category === "file");
|
|
61
|
+
const projectDirCompact = getInputProjectDir(input);
|
|
62
|
+
const { resolveProjectAttributions } = await loadProjectAttribution();
|
|
63
|
+
attributeAndInsertEvents(
|
|
64
|
+
db,
|
|
68
65
|
sessionId,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
[
|
|
67
|
+
{
|
|
68
|
+
type: "compaction_summary",
|
|
69
|
+
category: "compaction",
|
|
70
|
+
data: `Session compacted. ${events.length} events, ${fileEvents.length} files touched.`,
|
|
71
|
+
priority: 1,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: "snapshot-built",
|
|
75
|
+
category: "compaction",
|
|
76
|
+
data: `Snapshot built. ${snapshot.length} bytes for ${events.length} events.`,
|
|
77
|
+
priority: 1,
|
|
78
|
+
bytes_avoided: snapshot.length,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
input,
|
|
82
|
+
projectDirCompact,
|
|
75
83
|
"PreCompact",
|
|
76
|
-
|
|
77
|
-
{ bytesAvoided: snapshot.length, bytesReturned: 0 },
|
|
84
|
+
resolveProjectAttributions,
|
|
78
85
|
);
|
|
79
|
-
} catch { /* best-effort */ }
|
|
86
|
+
} catch { /* best-effort — never block PreCompact */ }
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
db.close();
|
|
@@ -83,13 +83,21 @@ export function attributeAndInsertEvents(db, sessionId, events, input, projectDi
|
|
|
83
83
|
// no event carries a positive value we leave bytesList undefined so
|
|
84
84
|
// SessionDB falls back to its 0-default for bytes_avoided/bytes_returned
|
|
85
85
|
// — preserves backward compat with older callers / tests.
|
|
86
|
+
// v1.0.160: handle both bytes_avoided (saved) and bytes_returned (resume
|
|
87
|
+
// snapshot replay) so the snapshot-consumed event from sessionstart.mjs
|
|
88
|
+
// routes through here without losing the bytes_returned column.
|
|
86
89
|
let bytesList;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
)
|
|
90
|
+
const hasBytes = events.some((e) =>
|
|
91
|
+
(typeof e?.bytes_avoided === "number" && e.bytes_avoided > 0) ||
|
|
92
|
+
(typeof e?.bytes_returned === "number" && e.bytes_returned > 0),
|
|
93
|
+
);
|
|
94
|
+
if (hasBytes) {
|
|
95
|
+
bytesList = events.map((e) => {
|
|
96
|
+
const avoided = typeof e?.bytes_avoided === "number" && e.bytes_avoided > 0 ? e.bytes_avoided : 0;
|
|
97
|
+
const returned = typeof e?.bytes_returned === "number" && e.bytes_returned > 0 ? e.bytes_returned : 0;
|
|
98
|
+
if (avoided === 0 && returned === 0) return undefined;
|
|
99
|
+
return { bytesAvoided: avoided, bytesReturned: returned };
|
|
100
|
+
});
|
|
93
101
|
}
|
|
94
102
|
// Prefer bulk path (single transaction = single WAL commit). Falls back
|
|
95
103
|
// to per-event insert for older SessionDB instances that lack bulkInsertEvents.
|
package/hooks/sessionstart.mjs
CHANGED
|
@@ -200,38 +200,37 @@ await runHook(async () => {
|
|
|
200
200
|
// D2 PRD Phase 6.2: emit snapshot-consumed with bytes_returned=snapshot.length.
|
|
201
201
|
// The resumed snapshot bytes ARE returned to the model — that's the whole
|
|
202
202
|
// point of resume — so account them on bytes_returned, not bytes_avoided.
|
|
203
|
+
// v1.0.160: route through wire — resume metric on the platform reads
|
|
204
|
+
// category='session-resume' rows. Both snapshot-consumed (bytes
|
|
205
|
+
// returned) and resume_completed land here so the dashboard sees
|
|
206
|
+
// every resume boundary.
|
|
203
207
|
try {
|
|
204
208
|
const resumeRow = (resume && resume.snapshot)
|
|
205
209
|
? resume
|
|
206
210
|
: (db.getResume?.(sessionId) ?? null);
|
|
207
211
|
const snapshotBytes = resumeRow?.snapshot?.length ?? 0;
|
|
212
|
+
const { resolveProjectAttributions } = await loadProjectAttribution();
|
|
213
|
+
const projectDirResumeMeta = getInputProjectDir(input);
|
|
208
214
|
|
|
209
|
-
|
|
215
|
+
await attributeAndInsertEvents(
|
|
216
|
+
db,
|
|
210
217
|
sessionId,
|
|
211
|
-
{
|
|
218
|
+
[{
|
|
212
219
|
type: "snapshot-consumed",
|
|
213
220
|
category: "session-resume",
|
|
214
221
|
data: `Session resumed from ${source}. Snapshot ${snapshotBytes} bytes injected.`,
|
|
215
222
|
priority: 1,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
undefined,
|
|
219
|
-
{ bytesAvoided: 0, bytesReturned: snapshotBytes },
|
|
220
|
-
);
|
|
221
|
-
} catch { /* best-effort */ }
|
|
222
|
-
|
|
223
|
-
// Legacy resume_completed event retained for back-compat with existing
|
|
224
|
-
// analytics consumers that filter on `type === 'resume_completed'`.
|
|
225
|
-
try {
|
|
226
|
-
db.insertEvent(
|
|
227
|
-
sessionId,
|
|
228
|
-
{
|
|
223
|
+
bytes_returned: snapshotBytes,
|
|
224
|
+
}, {
|
|
229
225
|
type: "resume_completed",
|
|
230
226
|
category: "session-resume",
|
|
231
227
|
data: `Session resumed from ${source}. Prior events loaded.`,
|
|
232
228
|
priority: 1,
|
|
233
|
-
},
|
|
229
|
+
}],
|
|
230
|
+
input,
|
|
231
|
+
projectDirResumeMeta,
|
|
234
232
|
"SessionStart",
|
|
233
|
+
resolveProjectAttributions,
|
|
235
234
|
);
|
|
236
235
|
} catch { /* best-effort */ }
|
|
237
236
|
}
|
|
@@ -322,22 +321,42 @@ await runHook(async () => {
|
|
|
322
321
|
// context at startup, invisible to PostToolUse hooks. We read them from
|
|
323
322
|
// disk so they survive compact/resume via the session events pipeline.
|
|
324
323
|
const sessionId = getSessionId(input);
|
|
325
|
-
|
|
324
|
+
// v1.0.160: cross-adapter projectDir resolution (was hardcoded CC env).
|
|
325
|
+
const projectDir = getInputProjectDir(input);
|
|
326
326
|
db.ensureSession(sessionId, projectDir);
|
|
327
327
|
const claudeMdPaths = [
|
|
328
328
|
join(resolveConfigDir(), "CLAUDE.md"),
|
|
329
329
|
join(projectDir, "CLAUDE.md"),
|
|
330
330
|
join(projectDir, ".claude", "CLAUDE.md"),
|
|
331
331
|
];
|
|
332
|
+
// v1.0.160: collect rule events into a batch and forward through wire.
|
|
333
|
+
// Dashboard's "CLAUDE.md adoption" widget COUNTs category='rule' rows on
|
|
334
|
+
// the platform — without this routing the widget reads 0 no matter how
|
|
335
|
+
// many CLAUDE.md files actually loaded.
|
|
336
|
+
const ruleEvents = [];
|
|
332
337
|
for (const p of claudeMdPaths) {
|
|
333
338
|
try {
|
|
334
339
|
const content = readFileSync(p, "utf-8");
|
|
335
340
|
if (content.trim()) {
|
|
336
|
-
|
|
337
|
-
|
|
341
|
+
ruleEvents.push({ type: "rule", category: "rule", data: p, priority: 1 });
|
|
342
|
+
ruleEvents.push({ type: "rule_content", category: "rule", data: content, priority: 1 });
|
|
338
343
|
}
|
|
339
344
|
} catch { /* file doesn't exist — skip */ }
|
|
340
345
|
}
|
|
346
|
+
if (ruleEvents.length > 0) {
|
|
347
|
+
try {
|
|
348
|
+
const { resolveProjectAttributions } = await loadProjectAttribution();
|
|
349
|
+
attributeAndInsertEvents(
|
|
350
|
+
db,
|
|
351
|
+
sessionId,
|
|
352
|
+
ruleEvents,
|
|
353
|
+
input,
|
|
354
|
+
projectDir,
|
|
355
|
+
"SessionStart",
|
|
356
|
+
resolveProjectAttributions,
|
|
357
|
+
);
|
|
358
|
+
} catch { /* best-effort — rule capture must never block start */ }
|
|
359
|
+
}
|
|
341
360
|
|
|
342
361
|
// Lifecycle anchor for a fresh session — emits BEFORE the CLAUDE.md
|
|
343
362
|
// rule events have been forwarded so the `session_start` row lands
|
|
@@ -77,8 +77,20 @@ await runHook(async () => {
|
|
|
77
77
|
workspaceRoots: Array.isArray(input.workspace_roots) ? input.workspace_roots : [],
|
|
78
78
|
lastKnownProjectDir: savedLastKnown || lastKnownProjectDir,
|
|
79
79
|
});
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// v1.0.160: route through wire so prompt-derived events (decision /
|
|
81
|
+
// role / intent / data extractions) reach the platform. Previously
|
|
82
|
+
// they only landed in local SessionDB → dashboard's prompt-flow
|
|
83
|
+
// insights stayed at 0.
|
|
84
|
+
if (userEvents.length > 0) {
|
|
85
|
+
attributeAndInsertEvents(
|
|
86
|
+
db,
|
|
87
|
+
sessionId,
|
|
88
|
+
userEvents,
|
|
89
|
+
input,
|
|
90
|
+
projectDir,
|
|
91
|
+
"UserPromptSubmit",
|
|
92
|
+
resolveProjectAttributions,
|
|
93
|
+
);
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
db.close();
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.160",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.160",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|