context-mode 1.0.22 → 1.0.24
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 +4 -2
- package/.openclaw-plugin/index.ts +11 -0
- package/.openclaw-plugin/openclaw.plugin.json +23 -0
- package/.openclaw-plugin/package.json +28 -0
- package/README.md +165 -26
- package/build/adapters/antigravity/index.d.ts +49 -0
- package/build/adapters/antigravity/index.js +217 -0
- package/build/adapters/client-map.d.ts +10 -0
- package/build/adapters/client-map.js +18 -0
- package/build/adapters/detect.d.ts +8 -1
- package/build/adapters/detect.js +58 -1
- package/build/adapters/kiro/hooks.d.ts +32 -0
- package/build/adapters/kiro/hooks.js +47 -0
- package/build/adapters/kiro/index.d.ts +50 -0
- package/build/adapters/kiro/index.js +325 -0
- package/build/adapters/openclaw/config.d.ts +8 -0
- package/build/adapters/openclaw/config.js +8 -0
- package/build/adapters/openclaw/hooks.d.ts +50 -0
- package/build/adapters/openclaw/hooks.js +61 -0
- package/build/adapters/openclaw/index.d.ts +51 -0
- package/build/adapters/openclaw/index.js +459 -0
- package/build/adapters/openclaw/session-db.d.ts +55 -0
- package/build/adapters/openclaw/session-db.js +88 -0
- package/build/adapters/types.d.ts +1 -1
- package/build/cli.js +5 -3
- package/build/executor.js +99 -112
- package/build/openclaw/workspace-router.d.ts +29 -0
- package/build/openclaw/workspace-router.js +64 -0
- package/build/openclaw-plugin.d.ts +121 -0
- package/build/openclaw-plugin.js +525 -0
- package/build/server.js +56 -10
- package/build/session/db.d.ts +9 -0
- package/build/session/db.js +38 -0
- package/cli.bundle.mjs +135 -124
- package/configs/antigravity/GEMINI.md +58 -0
- package/configs/antigravity/mcp_config.json +7 -0
- package/configs/kiro/mcp_config.json +7 -0
- package/configs/openclaw/AGENTS.md +58 -0
- package/configs/openclaw/openclaw.json +13 -0
- package/hooks/core/routing.mjs +12 -7
- package/hooks/kiro/posttooluse.mjs +58 -0
- package/hooks/kiro/pretooluse.mjs +63 -0
- package/hooks/session-db.bundle.mjs +2 -2
- package/hooks/session-helpers.mjs +41 -3
- package/openclaw.plugin.json +23 -0
- package/package.json +12 -1
- package/server.bundle.mjs +93 -82
- package/start.mjs +1 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# context-mode — MANDATORY routing rules
|
|
2
|
+
|
|
3
|
+
You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session. Antigravity does NOT have hooks, so these instructions are your ONLY enforcement mechanism. Follow them strictly.
|
|
4
|
+
|
|
5
|
+
## BLOCKED commands — do NOT use these
|
|
6
|
+
|
|
7
|
+
### curl / wget — FORBIDDEN
|
|
8
|
+
Do NOT use `curl` or `wget` via `run_command`. They dump raw HTTP responses directly into your context window.
|
|
9
|
+
Instead use:
|
|
10
|
+
- `mcp__context-mode__ctx_fetch_and_index(url, source)` to fetch and index web pages
|
|
11
|
+
- `mcp__context-mode__ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox
|
|
12
|
+
|
|
13
|
+
### Inline HTTP — FORBIDDEN
|
|
14
|
+
Do NOT run inline HTTP calls via `run_command` with `node -e "fetch(..."`, `python -c "requests.get(..."`, or similar patterns. They bypass the sandbox and flood context.
|
|
15
|
+
Instead use:
|
|
16
|
+
- `mcp__context-mode__ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context
|
|
17
|
+
|
|
18
|
+
### Direct web fetching — FORBIDDEN
|
|
19
|
+
Do NOT use `read_url_content` for large pages. Raw HTML can exceed 100 KB.
|
|
20
|
+
Instead use:
|
|
21
|
+
- `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` to query the indexed content
|
|
22
|
+
|
|
23
|
+
## REDIRECTED tools — use sandbox equivalents
|
|
24
|
+
|
|
25
|
+
### Shell (>20 lines output)
|
|
26
|
+
`run_command` is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
|
|
27
|
+
For everything else, use:
|
|
28
|
+
- `mcp__context-mode__ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
|
|
29
|
+
- `mcp__context-mode__ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context
|
|
30
|
+
|
|
31
|
+
### File reading (for analysis)
|
|
32
|
+
If you are reading a file to **edit** it → `view_file` / `replace_file_content` is correct (edit needs content in context).
|
|
33
|
+
If you are reading to **analyze, explore, or summarize** → use `mcp__context-mode__ctx_execute_file(path, language, code)` instead. Only your printed summary enters context. The raw file stays in the sandbox.
|
|
34
|
+
|
|
35
|
+
### Search (large results)
|
|
36
|
+
Search results can flood context. Use `mcp__context-mode__ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.
|
|
37
|
+
|
|
38
|
+
## Tool selection hierarchy
|
|
39
|
+
|
|
40
|
+
1. **GATHER**: `mcp__context-mode__ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls.
|
|
41
|
+
2. **FOLLOW-UP**: `mcp__context-mode__ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
|
|
42
|
+
3. **PROCESSING**: `mcp__context-mode__ctx_execute(language, code)` | `mcp__context-mode__ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
|
|
43
|
+
4. **WEB**: `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
|
|
44
|
+
5. **INDEX**: `mcp__context-mode__ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.
|
|
45
|
+
|
|
46
|
+
## Output constraints
|
|
47
|
+
|
|
48
|
+
- Keep responses under 500 words.
|
|
49
|
+
- Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description.
|
|
50
|
+
- When indexing content, use descriptive source labels so others can `search(source: "label")` later.
|
|
51
|
+
|
|
52
|
+
## ctx commands
|
|
53
|
+
|
|
54
|
+
| Command | Action |
|
|
55
|
+
|---------|--------|
|
|
56
|
+
| `ctx stats` | Call the `stats` MCP tool and display the full output verbatim |
|
|
57
|
+
| `ctx doctor` | Call the `doctor` MCP tool, run the returned shell command, display as checklist |
|
|
58
|
+
| `ctx upgrade` | Call the `upgrade` MCP tool, run the returned shell command, display as checklist |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# context-mode — MANDATORY routing rules
|
|
2
|
+
|
|
3
|
+
You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session.
|
|
4
|
+
|
|
5
|
+
## BLOCKED commands — do NOT attempt these
|
|
6
|
+
|
|
7
|
+
### curl / wget — BLOCKED
|
|
8
|
+
Any shell command containing `curl` or `wget` will be intercepted and blocked by the context-mode plugin. Do NOT retry.
|
|
9
|
+
Instead use:
|
|
10
|
+
- `mcp__context-mode__ctx_fetch_and_index(url, source)` to fetch and index web pages
|
|
11
|
+
- `mcp__context-mode__ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox
|
|
12
|
+
|
|
13
|
+
### Inline HTTP — BLOCKED
|
|
14
|
+
Any shell command containing `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, or `http.request(` will be intercepted and blocked. Do NOT retry with shell.
|
|
15
|
+
Instead use:
|
|
16
|
+
- `mcp__context-mode__ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context
|
|
17
|
+
|
|
18
|
+
### Direct web fetching — BLOCKED
|
|
19
|
+
Do NOT use any direct URL fetching tool. Use the sandbox equivalent.
|
|
20
|
+
Instead use:
|
|
21
|
+
- `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` to query the indexed content
|
|
22
|
+
|
|
23
|
+
## REDIRECTED tools — use sandbox equivalents
|
|
24
|
+
|
|
25
|
+
### Shell (>20 lines output)
|
|
26
|
+
Shell is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
|
|
27
|
+
For everything else, use:
|
|
28
|
+
- `mcp__context-mode__ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
|
|
29
|
+
- `mcp__context-mode__ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context
|
|
30
|
+
|
|
31
|
+
### File reading (for analysis)
|
|
32
|
+
If you are reading a file to **edit** it → reading is correct (edit needs content in context).
|
|
33
|
+
If you are reading to **analyze, explore, or summarize** → use `mcp__context-mode__ctx_execute_file(path, language, code)` instead. Only your printed summary enters context.
|
|
34
|
+
|
|
35
|
+
### grep / search (large results)
|
|
36
|
+
Search results can flood context. Use `mcp__context-mode__ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.
|
|
37
|
+
|
|
38
|
+
## Tool selection hierarchy
|
|
39
|
+
|
|
40
|
+
1. **GATHER**: `mcp__context-mode__ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls.
|
|
41
|
+
2. **FOLLOW-UP**: `mcp__context-mode__ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
|
|
42
|
+
3. **PROCESSING**: `mcp__context-mode__ctx_execute(language, code)` | `mcp__context-mode__ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
|
|
43
|
+
4. **WEB**: `mcp__context-mode__ctx_fetch_and_index(url, source)` then `mcp__context-mode__ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
|
|
44
|
+
5. **INDEX**: `mcp__context-mode__ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.
|
|
45
|
+
|
|
46
|
+
## Output constraints
|
|
47
|
+
|
|
48
|
+
- Keep responses under 500 words.
|
|
49
|
+
- Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description.
|
|
50
|
+
- When indexing content, use descriptive source labels so others can `search(source: "label")` later.
|
|
51
|
+
|
|
52
|
+
## ctx commands
|
|
53
|
+
|
|
54
|
+
| Command | Action |
|
|
55
|
+
|---------|--------|
|
|
56
|
+
| `ctx stats` | Call the `stats` MCP tool and display the full output verbatim |
|
|
57
|
+
| `ctx doctor` | Call the `doctor` MCP tool, run the returned shell command, display as checklist |
|
|
58
|
+
| `ctx upgrade` | Call the `upgrade` MCP tool, run the returned shell command, display as checklist |
|
package/hooks/core/routing.mjs
CHANGED
|
@@ -102,7 +102,6 @@ const TOOL_ALIASES = {
|
|
|
102
102
|
"grep_search": "Grep",
|
|
103
103
|
"search_file_content": "Grep",
|
|
104
104
|
"web_fetch": "WebFetch",
|
|
105
|
-
"activate_skill": "Agent",
|
|
106
105
|
// OpenCode
|
|
107
106
|
"bash": "Bash",
|
|
108
107
|
"view": "Read",
|
|
@@ -122,6 +121,10 @@ const TOOL_ALIASES = {
|
|
|
122
121
|
"Shell": "Bash",
|
|
123
122
|
// VS Code Copilot
|
|
124
123
|
"run_in_terminal": "Bash",
|
|
124
|
+
// Kiro CLI (https://kiro.dev/docs/cli/hooks/)
|
|
125
|
+
"fs_read": "Read",
|
|
126
|
+
"fs_write": "Write",
|
|
127
|
+
"execute_bash": "Bash",
|
|
125
128
|
};
|
|
126
129
|
|
|
127
130
|
/**
|
|
@@ -226,12 +229,14 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
|
|
|
226
229
|
// ─── Agent/Task: inject context-mode routing into subagent prompts ───
|
|
227
230
|
if (canonical === "Agent" || canonical === "Task") {
|
|
228
231
|
const subagentType = toolInput.subagent_type ?? "";
|
|
229
|
-
|
|
232
|
+
// Detect the correct field name for the prompt/request/objective/question/query
|
|
233
|
+
const fieldName = ["prompt", "request", "objective", "question", "query", "task"].find(f => f in toolInput) ?? "prompt";
|
|
234
|
+
const prompt = toolInput[fieldName] ?? "";
|
|
230
235
|
|
|
231
236
|
const updatedInput =
|
|
232
237
|
subagentType === "Bash"
|
|
233
|
-
? { ...toolInput,
|
|
234
|
-
: { ...toolInput,
|
|
238
|
+
? { ...toolInput, [fieldName]: prompt + ROUTING_BLOCK, subagent_type: "general-purpose" }
|
|
239
|
+
: { ...toolInput, [fieldName]: prompt + ROUTING_BLOCK };
|
|
235
240
|
|
|
236
241
|
return { action: "modify", updatedInput };
|
|
237
242
|
}
|
|
@@ -240,7 +245,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
|
|
|
240
245
|
// Match both __execute and __ctx_execute (prefixed tool names)
|
|
241
246
|
// Cursor can also surface the tool as MCP:ctx_execute_file.
|
|
242
247
|
if (
|
|
243
|
-
(toolName.includes("context-mode") && /__(ctx_)?execute$/.test(toolName)) ||
|
|
248
|
+
(toolName.includes("context-mode") && /(?:__|\/)(ctx_)?execute$/.test(toolName)) ||
|
|
244
249
|
/^MCP:(ctx_)?execute$/.test(toolName)
|
|
245
250
|
) {
|
|
246
251
|
if (security && toolInput.language === "shell") {
|
|
@@ -262,7 +267,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
|
|
|
262
267
|
// ─── MCP execute_file: check file path + code against deny patterns ───
|
|
263
268
|
// Cursor can also surface the tool as MCP:ctx_execute_file.
|
|
264
269
|
if (
|
|
265
|
-
(toolName.includes("context-mode") && /__(ctx_)?execute_file$/.test(toolName)) ||
|
|
270
|
+
(toolName.includes("context-mode") && /(?:__|\/)(ctx_)?execute_file$/.test(toolName)) ||
|
|
266
271
|
/^MCP:(ctx_)?execute_file$/.test(toolName)
|
|
267
272
|
) {
|
|
268
273
|
if (security) {
|
|
@@ -294,7 +299,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
|
|
|
294
299
|
}
|
|
295
300
|
|
|
296
301
|
// ─── MCP batch_execute: check each command individually ───
|
|
297
|
-
if (toolName.includes("context-mode") && /__(ctx_)?batch_execute$/.test(toolName)) {
|
|
302
|
+
if (toolName.includes("context-mode") && /(?:__|\/)(ctx_)?batch_execute$/.test(toolName)) {
|
|
298
303
|
if (security) {
|
|
299
304
|
const commands = toolInput.commands ?? [];
|
|
300
305
|
const policies = security.readBashPolicies(projectDir);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../suppress-stderr.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Kiro CLI PostToolUse hook — session event capture.
|
|
5
|
+
* Must be fast (<20ms). No network, no LLM, just SQLite writes.
|
|
6
|
+
*
|
|
7
|
+
* Source: https://kiro.dev/docs/cli/hooks/
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readStdin, getSessionId, getSessionDBPath, getInputProjectDir, KIRO_OPTS } from "../session-helpers.mjs";
|
|
11
|
+
import { appendFileSync } from "node:fs";
|
|
12
|
+
import { join, dirname } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
|
+
|
|
16
|
+
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
|
|
18
|
+
const OPTS = KIRO_OPTS;
|
|
19
|
+
const DEBUG_LOG = join(homedir(), ".kiro", "context-mode", "posttooluse-debug.log");
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const raw = await readStdin();
|
|
23
|
+
const input = JSON.parse(raw);
|
|
24
|
+
|
|
25
|
+
appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] CALL: ${input.tool_name}\n`);
|
|
26
|
+
|
|
27
|
+
const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
|
|
28
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
29
|
+
|
|
30
|
+
const dbPath = getSessionDBPath(OPTS);
|
|
31
|
+
const db = new SessionDB({ dbPath });
|
|
32
|
+
const sessionId = getSessionId(input, OPTS);
|
|
33
|
+
const projectDir = getInputProjectDir(input, OPTS);
|
|
34
|
+
|
|
35
|
+
db.ensureSession(sessionId, projectDir);
|
|
36
|
+
|
|
37
|
+
const events = extractEvents({
|
|
38
|
+
tool_name: input.tool_name,
|
|
39
|
+
tool_input: input.tool_input ?? {},
|
|
40
|
+
tool_response: typeof input.tool_response === "string"
|
|
41
|
+
? input.tool_response
|
|
42
|
+
: JSON.stringify(input.tool_response ?? ""),
|
|
43
|
+
tool_output: input.tool_output,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
for (const event of events) {
|
|
47
|
+
db.insertEvent(sessionId, event, "PostToolUse");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] OK: ${input.tool_name} → ${events.length} events\n`);
|
|
51
|
+
db.close();
|
|
52
|
+
} catch (err) {
|
|
53
|
+
try {
|
|
54
|
+
appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ERR: ${err?.message || err}\n`);
|
|
55
|
+
} catch { /* silent */ }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// PostToolUse is non-blocking — no stdout output
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../suppress-stderr.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Kiro CLI PreToolUse hook for context-mode.
|
|
5
|
+
* Uses exit codes instead of JSON stdout:
|
|
6
|
+
* - Exit 0: allow (stdout → agent context)
|
|
7
|
+
* - Exit 2: block (stderr → agent error)
|
|
8
|
+
*
|
|
9
|
+
* Source: https://kiro.dev/docs/cli/hooks/
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { dirname, resolve } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { readStdin } from "../core/stdin.mjs";
|
|
15
|
+
import { routePreToolUse, initSecurity } from "../core/routing.mjs";
|
|
16
|
+
|
|
17
|
+
const __hookDir = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
await initSecurity(resolve(__hookDir, "..", "..", "build"));
|
|
19
|
+
|
|
20
|
+
const raw = await readStdin();
|
|
21
|
+
const input = JSON.parse(raw);
|
|
22
|
+
// Kiro stdin: { hook_event_name, cwd, tool_name, tool_input }
|
|
23
|
+
const tool = input.tool_name ?? "";
|
|
24
|
+
const toolInput = input.tool_input ?? {};
|
|
25
|
+
const projectDir = input.cwd ?? process.cwd();
|
|
26
|
+
|
|
27
|
+
const decision = routePreToolUse(tool, toolInput, projectDir);
|
|
28
|
+
|
|
29
|
+
if (!decision) process.exit(0);
|
|
30
|
+
|
|
31
|
+
switch (decision.action) {
|
|
32
|
+
case "deny":
|
|
33
|
+
process.stderr.write(decision.reason ?? "Blocked by context-mode");
|
|
34
|
+
process.exit(2);
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case "modify":
|
|
38
|
+
// Kiro CLI cannot modify tool input — deny with redirect message
|
|
39
|
+
// The updatedInput.command contains an echo "..." wrapper — extract inner message
|
|
40
|
+
if (typeof decision.updatedInput?.command === "string") {
|
|
41
|
+
const msg = decision.updatedInput.command
|
|
42
|
+
.replace(/^echo\s+"?/, "")
|
|
43
|
+
.replace(/"?\s*$/, "");
|
|
44
|
+
process.stderr.write(msg);
|
|
45
|
+
} else {
|
|
46
|
+
process.stderr.write(decision.reason ?? "Blocked by context-mode routing");
|
|
47
|
+
}
|
|
48
|
+
process.exit(2);
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case "context":
|
|
52
|
+
process.stdout.write(decision.additionalContext ?? "");
|
|
53
|
+
process.exit(0);
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case "ask":
|
|
57
|
+
// Kiro CLI has no "ask" concept — passthrough
|
|
58
|
+
process.exit(0);
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
default:
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createRequire as
|
|
1
|
+
import{createRequire as p}from"node:module";import{unlinkSync as _}from"node:fs";import{tmpdir as T}from"node:os";import{join as g}from"node:path";var E=null;function h(){return E||(E=p(import.meta.url)("better-sqlite3")),E}function R(i){i.pragma("journal_mode = WAL"),i.pragma("synchronous = NORMAL")}function S(i){for(let t of["","-wal","-shm"])try{_(i+t)}catch{}}function d(i){try{i.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{i.close()}catch{}}function c(i="context-mode"){return g(T(),`${i}-${process.pid}.db`)}var a=class{#e;#t;constructor(t){let s=h();this.#e=t,this.#t=new s(t,{timeout:5e3}),R(this.#t),this.initSchema(),this.prepareStatements()}get db(){return this.#t}get dbPath(){return this.#e}close(){d(this.#t)}cleanup(){d(this.#t),S(this.#e)}};import{createHash as m}from"node:crypto";import{execFileSync as l}from"node:child_process";function U(){let i=process.env.CONTEXT_MODE_SESSION_SUFFIX;if(i!==void 0)return i?`__${i}`:"";try{let t=process.cwd(),s=l("git",["worktree","list","--porcelain"],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).split(/\r?\n/).find(n=>n.startsWith("worktree "))?.replace("worktree ","")?.trim();if(s&&t!==s)return`__${m("sha256").update(t).digest("hex").slice(0,8)}`}catch{}return""}var y=1e3,v=5,e={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",checkDuplicate:"checkDuplicate",evictLowestPriority:"evictLowestPriority",updateMetaLastEvent:"updateMetaLastEvent",ensureSession:"ensureSession",getSessionStats:"getSessionStats",incrementCompactCount:"incrementCompactCount",upsertResume:"upsertResume",getResume:"getResume",markResumeConsumed:"markResumeConsumed",deleteEvents:"deleteEvents",deleteMeta:"deleteMeta",deleteResume:"deleteResume",getOldSessions:"getOldSessions"},u=class extends a{constructor(t){super(t?.dbPath??c("session"))}stmt(t){return this.stmts.get(t)}initSchema(){try{let s=this.db.pragma("table_xinfo(session_events)").find(n=>n.name==="data_hash");s&&s.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
|
|
2
2
|
CREATE TABLE IF NOT EXISTS session_events (
|
|
3
3
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4
4
|
session_id TEXT NOT NULL,
|
|
@@ -54,4 +54,4 @@ import{createRequire as m}from"node:module";import{unlinkSync as p}from"node:fs"
|
|
|
54
54
|
snapshot = excluded.snapshot,
|
|
55
55
|
event_count = excluded.event_count,
|
|
56
56
|
created_at = datetime('now'),
|
|
57
|
-
consumed = 0`),t(e.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(e.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(e.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(e.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(e.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(e.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')")}insertEvent(t,s,n="PostToolUse"){let
|
|
57
|
+
consumed = 0`),t(e.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(e.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(e.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(e.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(e.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(e.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')")}insertEvent(t,s,n="PostToolUse"){let r=m("sha256").update(s.data).digest("hex").slice(0,16).toUpperCase();this.db.transaction(()=>{if(this.stmt(e.checkDuplicate).get(t,v,s.type,r))return;this.stmt(e.getEventCount).get(t).cnt>=y&&this.stmt(e.evictLowestPriority).run(t),this.stmt(e.insertEvent).run(t,s.type,s.category,s.priority,s.data,n,r),this.stmt(e.updateMetaLastEvent).run(t)})()}getEvents(t,s){let n=s?.limit??1e3,r=s?.type,o=s?.minPriority;return r&&o!==void 0?this.stmt(e.getEventsByTypeAndPriority).all(t,r,o,n):r?this.stmt(e.getEventsByType).all(t,r,n):o!==void 0?this.stmt(e.getEventsByPriority).all(t,o,n):this.stmt(e.getEvents).all(t,n)}getEventCount(t){return this.stmt(e.getEventCount).get(t).cnt}ensureSession(t,s){this.stmt(e.ensureSession).run(t,s)}getSessionStats(t){return this.stmt(e.getSessionStats).get(t)??null}incrementCompactCount(t){this.stmt(e.incrementCompactCount).run(t)}upsertResume(t,s,n){this.stmt(e.upsertResume).run(t,s,n??0)}getResume(t){return this.stmt(e.getResume).get(t)??null}markResumeConsumed(t){this.stmt(e.markResumeConsumed).run(t)}deleteSession(t){this.db.transaction(()=>{this.stmt(e.deleteEvents).run(t),this.stmt(e.deleteResume).run(t),this.stmt(e.deleteMeta).run(t)})()}cleanupOldSessions(t=7){let s=`-${t}`,n=this.stmt(e.getOldSessions).all(s);for(let{session_id:r}of n)this.deleteSession(r);return n.length}};export{u as SessionDB,U as getWorktreeSuffix};
|
|
@@ -7,11 +7,42 @@
|
|
|
7
7
|
* configuration. Defaults to Claude Code settings for backward compatibility.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
10
11
|
import { createHash } from "node:crypto";
|
|
11
12
|
import { join } from "node:path";
|
|
12
13
|
import { mkdirSync } from "node:fs";
|
|
13
14
|
import { homedir } from "node:os";
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Returns the worktree suffix for session path isolation.
|
|
18
|
+
* Mirrors the logic in src/server.ts — kept in sync manually since
|
|
19
|
+
* hooks run as plain .mjs (no TypeScript build step).
|
|
20
|
+
*/
|
|
21
|
+
function getWorktreeSuffix() {
|
|
22
|
+
const envSuffix = process.env.CONTEXT_MODE_SESSION_SUFFIX;
|
|
23
|
+
if (envSuffix !== undefined) {
|
|
24
|
+
return envSuffix ? `__${envSuffix}` : "";
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const mainWorktree = execFileSync(
|
|
29
|
+
"git",
|
|
30
|
+
["worktree", "list", "--porcelain"],
|
|
31
|
+
{ encoding: "utf-8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] },
|
|
32
|
+
)
|
|
33
|
+
.split(/\r?\n/)
|
|
34
|
+
.find((l) => l.startsWith("worktree "))
|
|
35
|
+
?.replace("worktree ", "")
|
|
36
|
+
?.trim();
|
|
37
|
+
if (mainWorktree && cwd !== mainWorktree) {
|
|
38
|
+
return `__${createHash("sha256").update(cwd).digest("hex").slice(0, 8)}`;
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// git not available or not a git repo — no suffix
|
|
42
|
+
}
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
|
|
15
46
|
/** Claude Code platform options (default). */
|
|
16
47
|
const CLAUDE_OPTS = {
|
|
17
48
|
configDir: ".claude",
|
|
@@ -40,6 +71,13 @@ export const CURSOR_OPTS = {
|
|
|
40
71
|
sessionIdEnv: "CURSOR_SESSION_ID",
|
|
41
72
|
};
|
|
42
73
|
|
|
74
|
+
/** Kiro CLI platform options. */
|
|
75
|
+
export const KIRO_OPTS = {
|
|
76
|
+
configDir: ".kiro",
|
|
77
|
+
projectDirEnv: undefined, // Kiro CLI provides cwd in hook stdin, no env var
|
|
78
|
+
sessionIdEnv: undefined, // No session ID env var — uses ppid fallback
|
|
79
|
+
};
|
|
80
|
+
|
|
43
81
|
/**
|
|
44
82
|
* Read all of stdin as a string (event-based, cross-platform safe).
|
|
45
83
|
*/
|
|
@@ -104,7 +142,7 @@ export function getSessionDBPath(opts = CLAUDE_OPTS) {
|
|
|
104
142
|
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
|
|
105
143
|
const dir = join(homedir(), opts.configDir, "context-mode", "sessions");
|
|
106
144
|
mkdirSync(dir, { recursive: true });
|
|
107
|
-
return join(dir, `${hash}.db`);
|
|
145
|
+
return join(dir, `${hash}${getWorktreeSuffix()}.db`);
|
|
108
146
|
}
|
|
109
147
|
|
|
110
148
|
/**
|
|
@@ -117,7 +155,7 @@ export function getSessionEventsPath(opts = CLAUDE_OPTS) {
|
|
|
117
155
|
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
|
|
118
156
|
const dir = join(homedir(), opts.configDir, "context-mode", "sessions");
|
|
119
157
|
mkdirSync(dir, { recursive: true });
|
|
120
|
-
return join(dir, `${hash}-events.md`);
|
|
158
|
+
return join(dir, `${hash}${getWorktreeSuffix()}-events.md`);
|
|
121
159
|
}
|
|
122
160
|
|
|
123
161
|
/**
|
|
@@ -130,5 +168,5 @@ export function getCleanupFlagPath(opts = CLAUDE_OPTS) {
|
|
|
130
168
|
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
|
|
131
169
|
const dir = join(homedir(), opts.configDir, "context-mode", "sessions");
|
|
132
170
|
mkdirSync(dir, { recursive: true });
|
|
133
|
-
return join(dir, `${hash}.cleanup`);
|
|
171
|
+
return join(dir, `${hash}${getWorktreeSuffix()}.cleanup`);
|
|
134
172
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "context-mode",
|
|
3
|
+
"name": "Context Mode",
|
|
4
|
+
"kind": "tool",
|
|
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.24",
|
|
7
|
+
"sandbox": {
|
|
8
|
+
"mode": "permissive",
|
|
9
|
+
"filesystem_access": "full",
|
|
10
|
+
"system_access": "full"
|
|
11
|
+
},
|
|
12
|
+
"configSchema": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"enabled": {
|
|
16
|
+
"type": "boolean",
|
|
17
|
+
"default": true,
|
|
18
|
+
"description": "Enable or disable the context-mode plugin."
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"additionalProperties": false
|
|
22
|
+
}
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.24",
|
|
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",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"gemini-cli",
|
|
14
14
|
"vscode-copilot",
|
|
15
15
|
"opencode",
|
|
16
|
+
"openclaw",
|
|
16
17
|
"codex-cli",
|
|
17
18
|
"context-window",
|
|
18
19
|
"sandbox",
|
|
@@ -25,11 +26,17 @@
|
|
|
25
26
|
"url": "https://github.com/mksglu/context-mode"
|
|
26
27
|
},
|
|
27
28
|
"homepage": "https://github.com/mksglu/context-mode#readme",
|
|
29
|
+
"openclaw": {
|
|
30
|
+
"extensions": [
|
|
31
|
+
"./build/openclaw-plugin.js"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
28
34
|
"bugs": "https://github.com/mksglu/context-mode/issues",
|
|
29
35
|
"main": "./build/opencode-plugin.js",
|
|
30
36
|
"exports": {
|
|
31
37
|
".": "./build/opencode-plugin.js",
|
|
32
38
|
"./plugin": "./build/opencode-plugin.js",
|
|
39
|
+
"./openclaw": "./build/openclaw-plugin.js",
|
|
33
40
|
"./cli": "./build/cli.js"
|
|
34
41
|
},
|
|
35
42
|
"bin": {
|
|
@@ -43,7 +50,9 @@
|
|
|
43
50
|
"cli.bundle.mjs",
|
|
44
51
|
"skills",
|
|
45
52
|
".claude-plugin",
|
|
53
|
+
".openclaw-plugin",
|
|
46
54
|
".mcp.json",
|
|
55
|
+
"openclaw.plugin.json",
|
|
47
56
|
"start.mjs",
|
|
48
57
|
"README.md",
|
|
49
58
|
"LICENSE"
|
|
@@ -51,6 +60,8 @@
|
|
|
51
60
|
"scripts": {
|
|
52
61
|
"build": "tsc && chmod +x build/cli.js",
|
|
53
62
|
"bundle": "esbuild src/server.ts --bundle --platform=node --target=node18 --format=esm --outfile=server.bundle.mjs --external:better-sqlite3 --external:turndown --external:turndown-plugin-gfm --external:@mixmark-io/domino --minify && esbuild src/cli.ts --bundle --platform=node --target=node18 --format=esm --outfile=cli.bundle.mjs --external:better-sqlite3 --minify && esbuild src/session/extract.ts --bundle --platform=node --target=node18 --format=esm --outfile=hooks/session-extract.bundle.mjs --minify && esbuild src/session/snapshot.ts --bundle --platform=node --target=node18 --format=esm --outfile=hooks/session-snapshot.bundle.mjs --minify && esbuild src/session/db.ts --bundle --platform=node --target=node18 --format=esm --outfile=hooks/session-db.bundle.mjs --external:better-sqlite3 --minify",
|
|
63
|
+
"version-sync": "node scripts/version-sync.mjs",
|
|
64
|
+
"version": "node scripts/version-sync.mjs && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json .openclaw-plugin/openclaw.plugin.json .openclaw-plugin/package.json openclaw.plugin.json",
|
|
54
65
|
"prepublishOnly": "npm run build",
|
|
55
66
|
"dev": "npx tsx src/server.ts",
|
|
56
67
|
"setup": "npx tsx src/cli.ts setup",
|