context-mode 1.0.135 → 1.0.136
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/hooks.json +65 -0
- package/.codex-plugin/mcp.json +9 -0
- package/.codex-plugin/plugin.json +31 -0
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +60 -12
- package/build/adapters/pi/mcp-bridge.d.ts +37 -1
- package/build/adapters/pi/mcp-bridge.js +135 -21
- package/build/lifecycle.d.ts +13 -13
- package/build/lifecycle.js +14 -14
- package/build/session/extract.js +39 -1
- package/cli.bundle.mjs +3 -3
- package/configs/kilo/kilo.json +9 -2
- package/configs/opencode/opencode.json +9 -2
- package/hooks/codex/platform.mjs +1 -0
- package/hooks/codex/posttooluse.mjs +1 -0
- package/hooks/codex/precompact.mjs +1 -0
- package/hooks/codex/pretooluse.mjs +1 -0
- package/hooks/codex/sessionstart.mjs +1 -0
- package/hooks/codex/stop.mjs +1 -0
- package/hooks/codex/userpromptsubmit.mjs +1 -0
- package/hooks/core/routing.mjs +112 -10
- package/hooks/ensure-deps.mjs +14 -3
- package/hooks/session-extract.bundle.mjs +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/server.bundle.mjs +4 -4
- package/build/openclaw-plugin.d.ts +0 -130
- package/build/openclaw-plugin.js +0 -626
- package/build/opencode-plugin.d.ts +0 -122
- package/build/opencode-plugin.js +0 -372
- package/build/pi-extension.d.ts +0 -14
- package/build/pi-extension.js +0 -451
- package/build/util/db-lock.d.ts +0 -65
- package/build/util/db-lock.js +0 -166
package/configs/kilo/kilo.json
CHANGED
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
"mcp": {
|
|
4
4
|
"context-mode": {
|
|
5
5
|
"type": "local",
|
|
6
|
-
"command": [
|
|
6
|
+
"command": [
|
|
7
|
+
"context-mode"
|
|
8
|
+
],
|
|
9
|
+
"environment": {
|
|
10
|
+
"CONTEXT_MODE_IDLE_TIMEOUT_MS": "900000"
|
|
11
|
+
}
|
|
7
12
|
}
|
|
8
13
|
},
|
|
9
|
-
"plugin": [
|
|
14
|
+
"plugin": [
|
|
15
|
+
"context-mode"
|
|
16
|
+
]
|
|
10
17
|
}
|
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
"mcp": {
|
|
4
4
|
"context-mode": {
|
|
5
5
|
"type": "local",
|
|
6
|
-
"command": [
|
|
6
|
+
"command": [
|
|
7
|
+
"context-mode"
|
|
8
|
+
],
|
|
9
|
+
"environment": {
|
|
10
|
+
"CONTEXT_MODE_IDLE_TIMEOUT_MS": "900000"
|
|
11
|
+
}
|
|
7
12
|
}
|
|
8
13
|
},
|
|
9
|
-
"plugin": [
|
|
14
|
+
"plugin": [
|
|
15
|
+
"context-mode"
|
|
16
|
+
]
|
|
10
17
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
process.env.CONTEXT_MODE_PLATFORM = "codex";
|
package/hooks/codex/stop.mjs
CHANGED
package/hooks/core/routing.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "../routing-block.mjs";
|
|
18
18
|
import { createToolNamer } from "./tool-naming.mjs";
|
|
19
19
|
import { isMCPReady } from "./mcp-ready.mjs";
|
|
20
|
-
import { existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, unlinkSync, openSync, closeSync, statSync, constants as fsConstants } from "node:fs";
|
|
20
|
+
import { existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, unlinkSync, openSync, closeSync, readFileSync, writeFileSync, statSync, constants as fsConstants } from "node:fs";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Guard for actions that redirect to MCP tools (#230).
|
|
@@ -29,7 +29,7 @@ function mcpRedirect(result) {
|
|
|
29
29
|
if (!isMCPReady()) return null;
|
|
30
30
|
return result;
|
|
31
31
|
}
|
|
32
|
-
import { tmpdir } from "node:os";
|
|
32
|
+
import { homedir, tmpdir } from "node:os";
|
|
33
33
|
import { resolve } from "node:path";
|
|
34
34
|
|
|
35
35
|
// Guidance throttle: show each advisory type at most once per session.
|
|
@@ -49,6 +49,32 @@ import { resolve } from "node:path";
|
|
|
49
49
|
// invocations of the same logical session.
|
|
50
50
|
const _guidanceShown = new Set();
|
|
51
51
|
|
|
52
|
+
// Periodic-guidance counters: how many times each (sessionId, type) pair has
|
|
53
|
+
// fired the periodic branch. Keyed by `${sessionId-or-ppid}::${type}`.
|
|
54
|
+
// File-backed for cross-process so hook invocations from the same logical
|
|
55
|
+
// session keep the counter coherent.
|
|
56
|
+
const _guidanceCounters = new Map();
|
|
57
|
+
|
|
58
|
+
// External-MCP nudge cadence — fire every N matching tool calls.
|
|
59
|
+
// Default 10: keeps the guidance fresh in long MCP-heavy sessions (e.g. a
|
|
60
|
+
// Jira/Slack/Notion run with 50+ tool calls — see #567 follow-up) without
|
|
61
|
+
// flooding context with repeat nudges. Bounds [1, 100]; invalid env values
|
|
62
|
+
// fall back to default. period=1 means "fire every call" (opt-in only).
|
|
63
|
+
const EXTERNAL_MCP_NUDGE_DEFAULT = 10;
|
|
64
|
+
const EXTERNAL_MCP_NUDGE_MIN = 1;
|
|
65
|
+
const EXTERNAL_MCP_NUDGE_MAX = 100;
|
|
66
|
+
const EXTERNAL_MCP_NUDGE_ENV = "CONTEXT_MODE_EXTERNAL_MCP_NUDGE_EVERY";
|
|
67
|
+
|
|
68
|
+
function getExternalMcpNudgeEvery() {
|
|
69
|
+
const raw = process.env[EXTERNAL_MCP_NUDGE_ENV];
|
|
70
|
+
if (raw == null || raw === "") return EXTERNAL_MCP_NUDGE_DEFAULT;
|
|
71
|
+
const parsed = Number.parseInt(raw, 10);
|
|
72
|
+
if (!Number.isFinite(parsed) || parsed < EXTERNAL_MCP_NUDGE_MIN || parsed > EXTERNAL_MCP_NUDGE_MAX) {
|
|
73
|
+
return EXTERNAL_MCP_NUDGE_DEFAULT;
|
|
74
|
+
}
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
function defaultGuidanceId() {
|
|
53
79
|
return process.env.VITEST_WORKER_ID
|
|
54
80
|
? `${process.ppid}-w${process.env.VITEST_WORKER_ID}`
|
|
@@ -85,6 +111,56 @@ function guidanceOnce(type, content, sessionId) {
|
|
|
85
111
|
return { action: "context", additionalContext: content };
|
|
86
112
|
}
|
|
87
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Like guidanceOnce, but fires on a periodic cadence (calls 1, period+1,
|
|
116
|
+
* 2·period+1, …) rather than once per session.
|
|
117
|
+
*
|
|
118
|
+
* Motivation: external-MCP tool runs can span 50+ calls (e.g. a Jira/Slack
|
|
119
|
+
* search loop — see #567 follow-up). A single one-shot nudge gets lost
|
|
120
|
+
* after the model's context compaction kicks in, and subsequent large MCP
|
|
121
|
+
* payloads flood context unchecked. Re-firing the nudge every N calls
|
|
122
|
+
* keeps the guidance in the model's recent window without saturating it.
|
|
123
|
+
*
|
|
124
|
+
* Counter state is process-aware: in-memory Map for same-process callers,
|
|
125
|
+
* file-backed `<guidanceDir>/<type>.count` for cross-process hook
|
|
126
|
+
* invocations. On any IO/parse failure we fall back to firing — losing a
|
|
127
|
+
* counter is preferable to silently dropping the advisory.
|
|
128
|
+
*/
|
|
129
|
+
function guidancePeriodic(type, content, sessionId, period) {
|
|
130
|
+
const safePeriod = Math.max(1, period | 0);
|
|
131
|
+
const id = sessionId ? `s-${sessionId}` : defaultGuidanceId();
|
|
132
|
+
const key = `${id}::${type}`;
|
|
133
|
+
|
|
134
|
+
// Read counter from memory first; fall through to disk on miss.
|
|
135
|
+
let count = _guidanceCounters.get(key);
|
|
136
|
+
const dir = guidanceDirFor(sessionId);
|
|
137
|
+
const counterPath = resolve(dir, `${type}.count`);
|
|
138
|
+
|
|
139
|
+
if (count == null) {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = Number.parseInt(readFileSync(counterPath, "utf8"), 10);
|
|
142
|
+
count = Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
143
|
+
} catch {
|
|
144
|
+
count = 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const next = count + 1;
|
|
149
|
+
_guidanceCounters.set(key, next);
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
mkdirSync(dir, { recursive: true });
|
|
153
|
+
writeFileSync(counterPath, String(next), "utf8");
|
|
154
|
+
} catch {
|
|
155
|
+
// Best-effort: cross-process counter may drift on FS failure, but we
|
|
156
|
+
// still return a decision based on the in-memory tick.
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Fire on the 1st, (period+1)th, (2·period+1)th… call.
|
|
160
|
+
if ((next - 1) % safePeriod !== 0) return null;
|
|
161
|
+
return { action: "context", additionalContext: content };
|
|
162
|
+
}
|
|
163
|
+
|
|
88
164
|
/**
|
|
89
165
|
* Robust recursive delete. On Windows, `fs.rmSync` on directories under a
|
|
90
166
|
* tmpdir whose path contains non-ASCII characters (e.g. a Chinese / Japanese /
|
|
@@ -105,6 +181,7 @@ function rmSyncRobust(dir) {
|
|
|
105
181
|
|
|
106
182
|
export function resetGuidanceThrottle(sessionId) {
|
|
107
183
|
_guidanceShown.clear();
|
|
184
|
+
_guidanceCounters.clear();
|
|
108
185
|
// Clear ppid-based dir (legacy / fallback callers) and the sessionId dir if given
|
|
109
186
|
rmSyncRobust(guidanceDirFor());
|
|
110
187
|
if (sessionId) {
|
|
@@ -499,6 +576,24 @@ function isExternalMcpTool(toolName) {
|
|
|
499
576
|
return false;
|
|
500
577
|
}
|
|
501
578
|
|
|
579
|
+
function getShellCommand(toolInput) {
|
|
580
|
+
if (!toolInput || typeof toolInput !== "object") return "";
|
|
581
|
+
if (typeof toolInput.command === "string") return toolInput.command;
|
|
582
|
+
if (typeof toolInput.cmd === "string") return toolInput.cmd;
|
|
583
|
+
return "";
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function getCodexConfigDir(env = process.env) {
|
|
587
|
+
const codexHome = env.CODEX_HOME;
|
|
588
|
+
if (codexHome && codexHome.trim() !== "") return resolve(codexHome);
|
|
589
|
+
return resolve(homedir(), ".codex");
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function getPlatformSettingsPath(platform) {
|
|
593
|
+
if (platform === "codex") return resolve(getCodexConfigDir(), "settings.json");
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
|
|
502
597
|
/**
|
|
503
598
|
* Route a PreToolUse event. Returns normalized decision object or null for passthrough.
|
|
504
599
|
*
|
|
@@ -539,17 +634,18 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
539
634
|
|
|
540
635
|
// Normalize platform-specific tool name to canonical
|
|
541
636
|
const canonical = TOOL_ALIASES[toolName] ?? toolName;
|
|
637
|
+
const platformSettingsPath = getPlatformSettingsPath(platform);
|
|
542
638
|
|
|
543
639
|
// ─── Bash: Stage 1 security check, then Stage 2 routing ───
|
|
544
640
|
if (canonical === "Bash") {
|
|
545
|
-
const command = toolInput
|
|
641
|
+
const command = getShellCommand(toolInput);
|
|
546
642
|
|
|
547
643
|
// Stage 1: Security check against user's deny/allow patterns.
|
|
548
644
|
// Only act when an explicit pattern matched. When no pattern matches,
|
|
549
645
|
// evaluateCommand returns { decision: "ask" } with no matchedPattern —
|
|
550
646
|
// in that case fall through so other hooks and the platform's native engine can decide.
|
|
551
647
|
if (security) {
|
|
552
|
-
const policies = security.readBashPolicies(projectDir);
|
|
648
|
+
const policies = security.readBashPolicies(projectDir, platformSettingsPath);
|
|
553
649
|
if (policies.length > 0) {
|
|
554
650
|
const result = security.evaluateCommand(command, policies);
|
|
555
651
|
if (result.decision === "deny") {
|
|
@@ -741,7 +837,7 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
741
837
|
if (matchesContextModeTool(toolName, "ctx_execute", "execute")) {
|
|
742
838
|
if (security && toolInput.language === "shell") {
|
|
743
839
|
const code = toolInput.code ?? "";
|
|
744
|
-
const policies = security.readBashPolicies(projectDir);
|
|
840
|
+
const policies = security.readBashPolicies(projectDir, platformSettingsPath);
|
|
745
841
|
if (policies.length > 0) {
|
|
746
842
|
const result = security.evaluateCommand(code, policies);
|
|
747
843
|
if (result.decision === "deny") {
|
|
@@ -760,7 +856,7 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
760
856
|
if (security) {
|
|
761
857
|
// Check file path against Read deny patterns
|
|
762
858
|
const filePath = toolInput.path ?? "";
|
|
763
|
-
const denyGlobs = security.readToolDenyPatterns("Read", projectDir);
|
|
859
|
+
const denyGlobs = security.readToolDenyPatterns("Read", projectDir, platformSettingsPath);
|
|
764
860
|
const evalResult = security.evaluateFilePath(filePath, denyGlobs);
|
|
765
861
|
if (evalResult.denied) {
|
|
766
862
|
return { action: "deny", reason: `Blocked by security policy: file path matches Read deny pattern ${evalResult.matchedPattern}` };
|
|
@@ -770,7 +866,7 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
770
866
|
const lang = toolInput.language ?? "";
|
|
771
867
|
const code = toolInput.code ?? "";
|
|
772
868
|
if (lang === "shell") {
|
|
773
|
-
const policies = security.readBashPolicies(projectDir);
|
|
869
|
+
const policies = security.readBashPolicies(projectDir, platformSettingsPath);
|
|
774
870
|
if (policies.length > 0) {
|
|
775
871
|
const result = security.evaluateCommand(code, policies);
|
|
776
872
|
if (result.decision === "deny") {
|
|
@@ -789,7 +885,7 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
789
885
|
if (matchesContextModeTool(toolName, "ctx_batch_execute", "batch_execute")) {
|
|
790
886
|
if (security) {
|
|
791
887
|
const commands = toolInput.commands ?? [];
|
|
792
|
-
const policies = security.readBashPolicies(projectDir);
|
|
888
|
+
const policies = security.readBashPolicies(projectDir, platformSettingsPath);
|
|
793
889
|
if (policies.length > 0) {
|
|
794
890
|
for (const entry of commands) {
|
|
795
891
|
const cmd = entry.command ?? "";
|
|
@@ -806,14 +902,20 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
806
902
|
return null;
|
|
807
903
|
}
|
|
808
904
|
|
|
809
|
-
// ─── External MCP tools:
|
|
905
|
+
// ─── External MCP tools: periodic guidance about routing large payloads ─── (#529, #567 follow-up)
|
|
810
906
|
// hooks/hooks.json registers a `mcp__(?!plugin_context-mode_)` matcher so this
|
|
811
907
|
// branch fires for slack/telegram/gdrive/notion-style MCPs whose results would
|
|
812
908
|
// otherwise spill into context. We don't deny or modify — the agent still needs
|
|
813
909
|
// the tool's output; we just nudge it to pipe large results through ctx_execute.
|
|
910
|
+
//
|
|
911
|
+
// Cadence: every N calls (default 10, tunable via CONTEXT_MODE_EXTERNAL_MCP_NUDGE_EVERY).
|
|
912
|
+
// The original one-shot nudge (#529) was lost after context compaction in
|
|
913
|
+
// MCP-heavy sessions (e.g. 50+ Jira calls in #567 follow-up), letting later
|
|
914
|
+
// payloads flood context unchecked. Re-firing periodically keeps the guidance
|
|
915
|
+
// in the model's recent window without saturating it.
|
|
814
916
|
if (isExternalMcpTool(toolName)) {
|
|
815
917
|
const externalMcpGuidance = platform ? createExternalMcpGuidance(t) : EXTERNAL_MCP_GUIDANCE;
|
|
816
|
-
return
|
|
918
|
+
return guidancePeriodic("external-mcp", externalMcpGuidance, sessionId, getExternalMcpNudgeEvery());
|
|
817
919
|
}
|
|
818
920
|
|
|
819
921
|
// Unknown tool — pass through
|
package/hooks/ensure-deps.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* @see https://github.com/mksglu/context-mode/issues/203
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { existsSync, copyFileSync } from "node:fs";
|
|
22
|
+
import { existsSync, copyFileSync, renameSync, unlinkSync } from "node:fs";
|
|
23
23
|
import { execSync } from "node:child_process";
|
|
24
24
|
import { resolve, dirname } from "node:path";
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -127,6 +127,18 @@ function probeNativeInProcess(pluginRoot) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
function replaceActiveNativeBinaryFromCache(abiCachePath, binaryPath) {
|
|
131
|
+
const tmpPath = `${binaryPath}.staging-${process.pid}-${Date.now()}`;
|
|
132
|
+
try {
|
|
133
|
+
copyFileSync(abiCachePath, tmpPath);
|
|
134
|
+
codesignBinary(tmpPath);
|
|
135
|
+
renameSync(tmpPath, binaryPath);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
try { unlinkSync(tmpPath); } catch { /* best effort cleanup */ }
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
130
142
|
export function ensureNativeCompat(pluginRoot) {
|
|
131
143
|
// Pre-compute paths regardless of runtime — the Bun branch below uses
|
|
132
144
|
// them to seed the ABI cache (#543) so the next /ctx-upgrade boot (under
|
|
@@ -161,8 +173,7 @@ export function ensureNativeCompat(pluginRoot) {
|
|
|
161
173
|
|
|
162
174
|
// Fast path: cached binary for this ABI already exists — swap in
|
|
163
175
|
if (existsSync(abiCachePath)) {
|
|
164
|
-
|
|
165
|
-
codesignBinary(binaryPath);
|
|
176
|
+
replaceActiveNativeBinaryFromCache(abiCachePath, binaryPath);
|
|
166
177
|
if (skipProbe) return; // Trust the cached binary — skip SIGSEGV-prone probe
|
|
167
178
|
// Validate via child process — dlopen cache is per-process, so in-process
|
|
168
179
|
// require() can't detect a swapped binary on disk (#148)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function
|
|
2
|
-
response: ${
|
|
1
|
+
function i(t){return t==null?"":String(t)}function E(t){return t==null?"":typeof t=="string"?t:JSON.stringify(t)}function d(t){let e=String(t.tool_response??""),n=t.tool_output?.isError===!0||t.tool_output?.is_error===!0;return t.tool_name==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(e)||n}function k(t){if(!t)return[];let e=[];for(let r of t.split(/\r?\n/)){if(r.startsWith("*** Add File: ")){e.push({path:r.slice(14).trim(),type:"file_write"});continue}if(r.startsWith("*** Update File: ")){e.push({path:r.slice(17).trim(),type:"file_edit"});continue}if(r.startsWith("*** Delete File: ")){e.push({path:r.slice(17).trim(),type:"file_edit"});continue}r.startsWith("*** Move to: ")&&e.push({path:r.slice(13).trim(),type:"file_edit"})}let n=new Set;return e.filter(r=>{if(!r.path)return!1;let o=`${r.type}:${r.path}`;return n.has(o)?!1:(n.add(o),!0)})}function S(t){return/(?:^|[/\\])\.claude[/\\]plans[/\\]/.test(t)}function A(t){let{tool_name:e,tool_input:n,tool_response:r}=t,o=[];if(e==="Read"){let s=String(n.file_path??"");return(/(?:CLAUDE|AGENTS(?:\.override)?|GEMINI|QWEN|KIRO)\.md$/i.test(s)||/\/copilot-instructions\.md$/i.test(s)||/\/context-mode\.mdc$/i.test(s)||/\.claude[\\/]/i.test(s)||/[\\/]memor(?:y|ies)[\\/][^\\/]+\.md$/i.test(s))&&(o.push({type:"rule",category:"rule",data:i(s),priority:1}),r&&r.length>0&&o.push({type:"rule_content",category:"rule",data:i(r),priority:1})),o.push({type:"file_read",category:"file",data:i(s),priority:1}),o}if(e==="Edit"){let s=String(n.file_path??"");return o.push({type:"file_edit",category:"file",data:i(s),priority:1}),o}if(e==="NotebookEdit"){let s=String(n.notebook_path??"");return o.push({type:"file_edit",category:"file",data:i(s),priority:1}),o}if(e==="Write"){let s=String(n.file_path??"");return o.push({type:"file_write",category:"file",data:i(s),priority:1}),o}if(e==="apply_patch"){if(d(t))return[];let s=k(String(n.command??n.patch??""));for(let a of s)o.push({type:a.type,category:"file",data:i(a.path),priority:1});return o}if(e==="Glob"){let s=String(n.pattern??"");return o.push({type:"file_glob",category:"file",data:i(s),priority:3}),o}if(e==="Grep"){let s=String(n.pattern??""),a=String(n.path??"");return o.push({type:"file_search",category:"file",data:i(`${s} in ${a}`),priority:3}),o}return o}function R(t){if(t.tool_name!=="Bash")return[];let n=String(t.tool_input.command??"").match(/\bcd\s+("([^"]+)"|'([^']+)'|(\S+))/);if(!n)return[];let r=n[2]??n[3]??n[4]??"";return[{type:"cwd",category:"cwd",data:i(r),priority:2}]}function T(t){let{tool_response:e}=t,n=String(e??"");return d(t)?[{type:"error_tool",category:"error",data:i(n),priority:2}]:[]}var x=[{pattern:/\bgit\s+checkout\b/,operation:"branch"},{pattern:/\bgit\s+commit\b/,operation:"commit"},{pattern:/\bgit\s+merge\s+\S+/,operation:"merge"},{pattern:/\bgit\s+rebase\b/,operation:"rebase"},{pattern:/\bgit\s+stash\b/,operation:"stash"},{pattern:/\bgit\s+push\b/,operation:"push"},{pattern:/\bgit\s+pull\b/,operation:"pull"},{pattern:/\bgit\s+log\b/,operation:"log"},{pattern:/\bgit\s+diff\b/,operation:"diff"},{pattern:/\bgit\s+status\b/,operation:"status"},{pattern:/\bgit\s+branch\b/,operation:"branch"},{pattern:/\bgit\s+reset\b/,operation:"reset"},{pattern:/\bgit\s+add\b/,operation:"add"},{pattern:/\bgit\s+cherry-pick\b/,operation:"cherry-pick"},{pattern:/\bgit\s+tag\b/,operation:"tag"},{pattern:/\bgit\s+fetch\b/,operation:"fetch"},{pattern:/\bgit\s+clone\b/,operation:"clone"},{pattern:/\bgit\s+worktree\b/,operation:"worktree"}];function w(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??""),n=x.find(r=>r.pattern.test(e));return n?[{type:"git",category:"git",data:i(n.operation),priority:2}]:[]}function I(t){return new Set(["TodoWrite","TaskCreate","TaskUpdate"]).has(t.tool_name)?[{type:t.tool_name==="TaskUpdate"?"task_update":t.tool_name==="TaskCreate"?"task_create":"task",category:"task",data:i(JSON.stringify(t.tool_input)),priority:1}]:[]}function N(t){if(t.tool_name==="EnterPlanMode")return[{type:"plan_enter",category:"plan",data:"entered plan mode",priority:2}];if(t.tool_name==="ExitPlanMode"){let e=[],n=t.tool_input.allowedPrompts,r=Array.isArray(n)&&n.length>0?`exited plan mode (allowed: ${E(n.map(s=>typeof s=="object"&&s!==null&&"prompt"in s?String(s.prompt):String(s)).join(", "))})`:"exited plan mode";e.push({type:"plan_exit",category:"plan",data:i(r),priority:2});let o=String(t.tool_response??"").toLowerCase();return o.includes("approved")||o.includes("approve")?e.push({type:"plan_approved",category:"plan",data:"plan approved by user",priority:1}):(o.includes("rejected")||o.includes("decline")||o.includes("denied"))&&e.push({type:"plan_rejected",category:"plan",data:i(`plan rejected: ${t.tool_response??""}`),priority:2}),e}if(t.tool_name==="Write"||t.tool_name==="Edit"){let e=String(t.tool_input.file_path??"");if(S(e))return[{type:"plan_file_write",category:"plan",data:i(`plan file: ${e.split(/[/\\]/).pop()??e}`),priority:2}]}return t.tool_name==="apply_patch"?d(t)?[]:k(String(t.tool_input.command??t.tool_input.patch??"")).filter(n=>S(n.path)).map(n=>({type:"plan_file_write",category:"plan",data:i(`plan file: ${n.path.split(/[/\\]/).pop()??n.path}`),priority:2})):[]}var C=[/\bsource\s+\S*activate\b/,/\bexport\s+\w+=/,/\bnvm\s+use\b/,/\bpyenv\s+(shell|local|global)\b/,/\bconda\s+activate\b/,/\brbenv\s+(shell|local|global)\b/,/\bnpm\s+install\b/,/\bnpm\s+ci\b/,/\bpip\s+install\b/,/\bbun\s+install\b/,/\byarn\s+(add|install)\b/,/\bpnpm\s+(add|install)\b/,/\bcargo\s+(install|add)\b/,/\bgo\s+(install|get)\b/,/\brustup\b/,/\basdf\b/,/\bvolta\b/,/\bdeno\s+install\b/];function H(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??"");if(!C.some(o=>o.test(e)))return[];let r=e.replace(/\bexport\s+(\w+)=\S*/g,"export $1=***");return[{type:"env",category:"env",data:i(r),priority:2}]}function L(t){if(t.tool_name!=="Skill")return[];let e=String(t.tool_input.skill??"");return[{type:"skill",category:"skill",data:i(e),priority:2}]}function $(t){if(!t.tool_response?.includes("Error")&&!t.tool_output?.isError)return[];let e=String(t.tool_response||""),n=[/not supported/i,/cannot/i,/does not support/i,/FAIL/i,/refused/i,/permission denied/i,/incompatible/i];for(let r of n){let o=e.match(r);if(o){let s=e.toLowerCase().indexOf(o[0].toLowerCase()),a=e.slice(Math.max(0,s-50),Math.min(e.length,s+200)).trim();return[{type:"constraint_discovered",category:"constraint",data:i(a),priority:2}]}}return[]}function O(t){if(t.tool_name!=="Agent")return[];let e=i(String(t.tool_input.prompt??t.tool_input.description??"")),n=t.tool_response?i(String(t.tool_response)):"",r=n.length>0;return[{type:r?"subagent_completed":"subagent_launched",category:"subagent",data:i(r?`[completed] ${e} \u2192 ${n}`:`[launched] ${e}`),priority:r?2:3}]}function P(t){let{tool_name:e,tool_input:n,tool_response:r}=t;if(!e.startsWith("mcp__"))return[];let o=e.split("__"),s=o[o.length-1]||e,a=Object.values(n).find(g=>typeof g=="string"),u=a?`: ${i(String(a))}`:"",c=r&&r.length>0?`
|
|
2
|
+
response: ${i(r)}`:"";return[{type:"mcp",category:"mcp",data:i(`${s}${u}${c}`),priority:3}]}var M=2048;function B(t,e){if(Buffer.byteLength(t,"utf8")<=e)return{value:t,truncated:!1};let n=Buffer.from(t,"utf8"),r=e;for(;r>0&&(n[r]&192)===128;)r--;return{value:n.subarray(0,r).toString("utf8"),truncated:!0}}var W=/(authorization|auth_token|access_token|refresh_token|bearer|token|secret|password|passwd|pwd|api[-_]?key|apikey|cookie|set-cookie|signature|private[-_]?key|client[-_]?secret|x[-_]?api[-_]?key)/i,j="[REDACTED]";function y(t,e=new WeakSet){if(t==null||typeof t!="object")return t;if(e.has(t))return"[CIRCULAR]";e.add(t);let n;if(Array.isArray(t))n=t.map(r=>y(r,e));else{let r={};for(let[o,s]of Object.entries(t))W.test(o)?r[o]=j:r[o]=y(s,e);n=r}return e.delete(t),n}function F(t){let{tool_name:e,tool_input:n}=t;if(!e.startsWith("mcp__"))return[];let r=y(n??{}),o;try{o=JSON.stringify(r)}catch{o="{}"}let{value:s,truncated:a}=B(o,M),u=a?`{"tool_name":${JSON.stringify(e)},"params_raw":${JSON.stringify(s)},"truncated":true}`:`{"tool_name":${JSON.stringify(e)},"params":${s}}`;return[{type:"mcp_tool_call",category:"mcp_tool_call",data:i(u),priority:4}]}function D(t){if(t.tool_name!=="AskUserQuestion")return[];let e=t.tool_input.questions,n=Array.isArray(e)&&e.length>0?String(e[0].question??""):"",r=String(t.tool_response??""),o="";try{let c=JSON.parse(r)?.answers;if(c&&typeof c=="object"){let g=f=>typeof f=="string"?f:Array.isArray(f)?f.filter(h=>typeof h=="string").join(" | "):"",m=n?g(c[n]):"";m?o=m:o=Object.values(c).map(g).filter(h=>h.length>0).join(" | ")}}catch{}let s=i(o),a=n?`Q: ${i(n)} \u2192 A: ${s}`:`answer: ${s}`;return[{type:"decision_question",category:"decision",data:i(a),priority:2}]}function U(t){if(t.tool_name!=="Agent")return[];if(!t.tool_response||t.tool_response.length===0)return[];let e=t.tool_response.length>500?t.tool_response.slice(0,500):t.tool_response;return[{type:"agent_finding",category:"agent-finding",data:i(e),priority:2}]}function K(t){let e=[E(t.tool_input),i(t.tool_response)].join(" ");if(e.length===0)return[];let n=new Set,r=e.match(/https?:\/\/[^\s)]+/g);if(r)for(let c of r)c=c.replace(/["'})\],;.]+$/,""),/localhost|127\.0\.0\.1/i.test(c)||n.add(c);let o=e.match(/(?<!\w)#(\d+)/g);if(o)for(let c of o)n.add(c);if(n.size===0)return[];let s,a=i(t.tool_response).match(/Fetched and indexed[^\(]*\(([\d.]+)\s*KB\)/i);if(a){let c=Number(a[1]);Number.isFinite(c)&&c>0&&(s=Math.round(c*1024))}let u={type:"external_ref",category:"external-ref",data:i(Array.from(n).join(", ")),priority:3};return s!==void 0&&(u.bytes_avoided=s),[u]}function G(t){if(t.tool_name!=="EnterWorktree")return[];let e=String(t.tool_input.name??"unnamed");return[{type:"worktree",category:"env",data:i(`entered worktree: ${e}`),priority:2}]}var v=/[,;,;、،]/u,J=15,q=500;function z(t){if(_.test(t)||!b.test(t)||!v.test(t))return!1;let e=[...t].length;return e>=J&&e<=q}function Q(t){let e=t.trim();return z(e)?[{type:"decision",category:"decision",data:i(t),priority:2}]:[]}var V=8,X=120,Y=new RegExp("\\p{L}+\\s+\\p{L}+","u"),Z=new RegExp("\\p{L}{6,}","u");function tt(t){let e=t.split(/[.!\n。!]/u)[0].trim();if(_.test(e)||v.test(e)||!b.test(e))return!1;let n=[...e].length;return n<V||n>X?!1:Y.test(e)||Z.test(e)}function et(t){let e=t.trim();return tt(e)?[{type:"role",category:"role",data:i(t),priority:3}]:[]}var _=/[??؟¿]/u,b=new RegExp("\\p{L}","u"),nt=60;function rt(t){if(_.test(t)||!b.test(t))return!1;let e=[...t].length;return e>0&&e<nt}function ot(t){let e=t.trim();if(!e)return[];let n;return _.test(e)?n="investigate":rt(e)&&(n="implement"),n?[{type:"intent",category:"intent",data:i(n),priority:4}]:[]}var st=/(?:\bError\s*:|\bException\s*:|\bTraceback\b|\bat\s+\S+\s*\([^)]*:\d+:\d+\))/u,it=/[✓✔✅☑🎉]/u,at=/^\s*(?:fixed|resolved)\s*:/iu;function ct(t){let e=[];return it.test(t)||at.test(t)?(e.push({type:"blocker_resolved",category:"blocked-on",data:i(t),priority:2}),e):(st.test(t)&&e.push({type:"blocker",category:"blocked-on",data:i(t),priority:2}),e)}function lt(t){return t.length<=1024?[]:[{type:"data",category:"data",data:i(t),priority:4}]}var l=null;function pt(t){let{tool_name:e,tool_response:n}=t,r=String(n??"");if(d(t))return l={tool:e,error:r.slice(0,200),callsSince:0},[];if(!l)return[];if(l.callsSince++,l.callsSince>10)return l=null,[];if(!!d(t))return[];let s=e===l.tool,a=l.tool==="Read"&&(e==="Edit"||e==="Write"||e==="apply_patch");if(s||a){let u={type:"error_resolved",category:"error-resolution",data:i(`Error in ${l.tool}: ${l.error} \u2192 Fixed`),priority:2};return l=null,[u]}return[]}function _t(){l=null}var p=[];function ut(t){return`${t.length}:${t.slice(0,20)}`}function ft(t){let{tool_name:e,tool_input:n}=t,r=ut(JSON.stringify(n).slice(0,200));if(p.push({tool:e,inputHash:r}),p.length>50&&p.splice(0,p.length-50),p.length<3)return[];let o=0;for(let s=p.length-1;s>=0&&(p[s].tool===e&&p[s].inputHash===r);s--)o++;return o>=3?(p.splice(p.length-o),[{type:"retry_detected",category:"iteration-loop",data:i(`${e} called ${o} times with similar input`),priority:2}]):[]}function ht(){p.length=0}var dt={run_shell_command:"Bash",read_file:"Read",read_many_files:"Read",grep_search:"Grep",search_file_content:"Grep",web_fetch:"WebFetch",write_file:"Write",edit:"Edit",glob:"Glob",todo_write:"TodoWrite",ask_user_question:"AskUserQuestion",list_directory:"LS",save_memory:"Memory",skill:"Skill",exit_plan_mode:"ExitPlanMode",agent:"Agent",bash:"Bash",view:"Read",grep:"Grep",fetch:"WebFetch",shell:"Bash",shell_command:"Bash",exec_command:"Bash","container.exec":"Bash",local_shell:"Bash",grep_files:"Grep"};function gt(t){let e=dt[t.tool_name];return!e||e===t.tool_name?t:{...t,tool_name:e}}function yt(t){try{let e=gt(t),n=[];return n.push(...A(e)),n.push(...R(e)),n.push(...T(e)),n.push(...w(e)),n.push(...H(e)),n.push(...I(e)),n.push(...N(e)),n.push(...L(e)),n.push(...O(e)),n.push(...P(e)),n.push(...F(e)),n.push(...D(e)),n.push(...$(e)),n.push(...G(e)),n.push(...U(e)),n.push(...K(e)),n.push(...pt(e)),n.push(...ft(e)),n}catch{return[]}}function bt(t){try{let e=[];return e.push(...Q(t)),e.push(...et(t)),e.push(...ot(t)),e.push(...ct(t)),e.push(...lt(t)),e}catch{return[]}}export{yt as extractEvents,bt as extractUserEvents,_t as resetErrorResolutionState,ht as resetIterationLoopState};
|
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.136",
|
|
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.136",
|
|
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",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"bin",
|
|
74
74
|
"skills",
|
|
75
75
|
".claude-plugin",
|
|
76
|
+
".codex-plugin",
|
|
76
77
|
".openclaw-plugin",
|
|
77
78
|
"openclaw.plugin.json",
|
|
78
79
|
"start.mjs",
|