context-mode 1.0.134 → 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/detect.d.ts +3 -1
- package/build/adapters/detect.js +7 -2
- package/build/adapters/pi/mcp-bridge.d.ts +44 -0
- package/build/adapters/pi/mcp-bridge.js +149 -3
- package/build/cli.js +17 -0
- package/build/lifecycle.d.ts +13 -13
- package/build/lifecycle.js +14 -14
- package/build/runtime.js +8 -5
- package/build/session/analytics.d.ts +0 -13
- package/build/session/analytics.js +50 -1
- package/build/session/extract.js +39 -1
- package/build/util/claude-config.d.ts +12 -6
- package/build/util/claude-config.js +16 -23
- package/cli.bundle.mjs +135 -133
- 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 +24 -1
- package/hooks/codex/stop.mjs +1 -0
- package/hooks/codex/userpromptsubmit.mjs +1 -0
- package/hooks/core/platform-detect.mjs +1 -1
- package/hooks/core/routing.mjs +112 -10
- package/hooks/ensure-deps.mjs +14 -3
- package/hooks/normalize-hooks.mjs +5 -2
- package/hooks/security.bundle.mjs +1 -1
- package/hooks/session-extract.bundle.mjs +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/heal-installed-plugins.mjs +67 -0
- package/server.bundle.mjs +99 -99
- package/start.mjs +73 -11
- 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";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./platform.mjs";
|
|
2
3
|
import "../suppress-stderr.mjs";
|
|
3
4
|
import "../ensure-deps.mjs";
|
|
4
5
|
/**
|
|
@@ -23,10 +24,12 @@ import {
|
|
|
23
24
|
getSessionEventsPath,
|
|
24
25
|
getCleanupFlagPath,
|
|
25
26
|
getInputProjectDir,
|
|
27
|
+
resolveConfigDir,
|
|
26
28
|
CODEX_OPTS,
|
|
27
29
|
} from "../session-helpers.mjs";
|
|
28
30
|
import { createSessionLoaders } from "../session-loaders.mjs";
|
|
29
|
-
import { unlinkSync } from "node:fs";
|
|
31
|
+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
32
|
+
import { join } from "node:path";
|
|
30
33
|
import { fileURLToPath } from "node:url";
|
|
31
34
|
|
|
32
35
|
const HOOK_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -35,6 +38,25 @@ const OPTS = CODEX_OPTS;
|
|
|
35
38
|
|
|
36
39
|
let additionalContext = ROUTING_BLOCK;
|
|
37
40
|
|
|
41
|
+
function captureCodexInstructionRules(db, sessionId, projectDir) {
|
|
42
|
+
const paths = [];
|
|
43
|
+
for (const baseDir of [resolveConfigDir(OPTS), projectDir]) {
|
|
44
|
+
paths.push(join(baseDir, "AGENTS.md"));
|
|
45
|
+
paths.push(join(baseDir, "AGENTS.override.md"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const p of [...new Set(paths)]) {
|
|
49
|
+
try {
|
|
50
|
+
if (!existsSync(p)) continue;
|
|
51
|
+
const content = readFileSync(p, "utf8");
|
|
52
|
+
db.insertEvent(sessionId, { type: "rule", category: "rule", data: p, priority: 1 });
|
|
53
|
+
db.insertEvent(sessionId, { type: "rule_content", category: "rule", data: content, priority: 1 });
|
|
54
|
+
} catch {
|
|
55
|
+
// Missing or unreadable rule files should never break SessionStart.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
38
60
|
try {
|
|
39
61
|
const raw = await readStdin();
|
|
40
62
|
const input = parseStdin(raw);
|
|
@@ -84,6 +106,7 @@ try {
|
|
|
84
106
|
|
|
85
107
|
const sessionId = getSessionId(input, OPTS);
|
|
86
108
|
db.ensureSession(sessionId, projectDir);
|
|
109
|
+
captureCodexInstructionRules(db, sessionId, projectDir);
|
|
87
110
|
|
|
88
111
|
db.close();
|
|
89
112
|
}
|
package/hooks/codex/stop.mjs
CHANGED
|
@@ -26,7 +26,7 @@ const PLATFORM_ENV_VARS_MIRROR = [
|
|
|
26
26
|
["antigravity", ["ANTIGRAVITY_CLI_ALIAS"]],
|
|
27
27
|
["cursor", ["CURSOR_TRACE_ID", "CURSOR_CLI"]],
|
|
28
28
|
["kilo", ["KILO_PID"]],
|
|
29
|
-
["opencode", ["OPENCODE", "OPENCODE_PID"]],
|
|
29
|
+
["opencode", ["OPENCODE_CLIENT", "OPENCODE_TERMINAL", "OPENCODE", "OPENCODE_PID"]],
|
|
30
30
|
["zed", ["ZED_SESSION_ID", "ZED_TERM"]],
|
|
31
31
|
["codex", ["CODEX_THREAD_ID", "CODEX_CI"]],
|
|
32
32
|
["gemini-cli", ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]],
|
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)
|
|
@@ -142,12 +142,15 @@ export function normalizePluginJson(content, nodePath, pluginRoot) {
|
|
|
142
142
|
* Options:
|
|
143
143
|
* - pluginRoot: absolute path to plugin install dir (e.g. __dirname of start.mjs)
|
|
144
144
|
* - nodePath: process.execPath
|
|
145
|
-
* - platform: process.platform (
|
|
145
|
+
* - platform: process.platform ("win32" and "linux" trigger a write)
|
|
146
146
|
*
|
|
147
147
|
* Best-effort — never throws.
|
|
148
148
|
*/
|
|
149
149
|
export function normalizeHooksOnStartup({ pluginRoot, nodePath, platform }) {
|
|
150
|
-
|
|
150
|
+
// Normalize on Windows (MSYS path mangling, #369/#372/#378) and Linux
|
|
151
|
+
// (bare `node` not in PATH when invoked via /bin/sh, e.g. nvm users).
|
|
152
|
+
// macOS ships a system node so bare `node` resolves reliably there.
|
|
153
|
+
if (platform !== "win32" && platform !== "linux") return;
|
|
151
154
|
if (!pluginRoot || !nodePath) return;
|
|
152
155
|
|
|
153
156
|
// hooks/hooks.json
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as S,realpathSync as R}from"node:fs";import{resolve as u}from"node:path";import{resolve as a}from"node:path";import{homedir as d}from"node:os";import{createRequire as C}from"node:module";function v(e=process.env){let s=e.CLAUDE_CONFIG_DIR;return s&&s.trim()!==""?s.startsWith("~")?a(d(),s.replace(/^~[/\\]?/,"")):a(s):a(d(),".claude")}function E(e=process.env){return a(v(e),"settings.json")}function h(e=process.env){let s=[],t=null,n=null;try{let r=C(import.meta.url)("../adapters/detect.js");t=r.detectPlatform(),n=r.getSessionDirSegments}catch{}if(t&&n&&t.platform!=="claude-code"){let o=n(t.platform);o&&o.length>0&&s.push(a(d(),...o,"settings.json"))}let i=E(e);return s.includes(i)||s.push(i),s}function w(e){let s=e.match(/^Bash\((.+)\)$/);return s?s[1]:null}function k(e){let s=e.match(/^(\w+)\((.+)\)$/);return s?{tool:s[1],glob:s[2]}:null}function $(e){return e.replace(/[.*+?^${}()|[\]\\\/\-]/g,"\\$&")}function P(e){return e.replace(/[.+?^${}()|[\]\\\/\-]/g,"\\$&").replace(/\*/g,".*")}function A(e,s=!1){let t,n=e.indexOf(":");if(n!==-1){let i=e.slice(0,n),o=e.slice(n+1),r=$(i),l=P(o);t=`^${r}(\\s${l})?$`}else t=`^${P(e)}$`;return new RegExp(t,s?"i":"")}function G(e,s=!1){let t="",n=0;for(;n<e.length;)e[n]==="*"&&e[n+1]==="*"?n+2<e.length&&e[n+2]==="/"?(t+="(.*/)?",n+=3):(t+=".*",n+=2):e[n]==="*"?(t+="[^/]*",n++):e[n]==="?"?(t+="[^/]",n++):(t+=e[n].replace(/[.+^${}()|[\]\\\/\-]/g,"\\$&"),n++);return new RegExp(`^${t}$`,s?"i":"")}function f(e,s,t=!1){for(let n of s){let i=w(n);if(i&&A(i,t).test(e))return n}return null}function b(e){let s=[],t="",n=!1,i=!1,o=!1;for(let r=0;r<e.length;r++){let l=e[r],c=r>0?e[r-1]:"";l==="'"&&!i&&!o&&c!=="\\"?(n=!n,t+=l):l==='"'&&!n&&!o&&c!=="\\"?(i=!i,t+=l):l==="`"&&!n&&!i&&c!=="\\"?(o=!o,t+=l):!n&&!i&&!o?l===";"?(s.push(t.trim()),t=""):l==="|"&&e[r+1]==="|"||l==="&"&&e[r+1]==="&"?(s.push(t.trim()),t="",r++):l==="|"?(s.push(t.trim()),t=""):t+=l:t+=l}return t.trim()&&s.push(t.trim()),s.filter(r=>r.length>0)}function m(e){let s;try{s=S(e,"utf-8")}catch{return null}let t;try{t=JSON.parse(s)}catch{return null}let n=t?.permissions;if(!n||typeof n!="object")return null;let i=o=>Array.isArray(o)?o.filter(r=>typeof r=="string"&&w(r)!==null):[];return{allow:i(n.allow),deny:i(n.deny),ask:i(n.ask)}}function L(e,s){let t=[];if(e){let i=u(e,".claude","settings.local.json"),o=m(i);o&&t.push(o);let r=u(e,".claude","settings.json"),l=m(r);l&&t.push(l)}let n=s!==void 0?[s]:h();for(let i of n){let o=m(i);o&&t.push(o)}return t}function M(e,s,t){let n=[],i=r=>{let l;try{l=S(r,"utf-8")}catch{return null}let c;try{c=JSON.parse(l)}catch{return null}let g=c?.permissions?.deny;if(!Array.isArray(g))return[];let y=[];for(let x of g){if(typeof x!="string")continue;let p=k(x);p&&p.tool===e&&y.push(p.glob)}return y};if(s){let r=i(u(s,".claude","settings.local.json"));r!==null&&n.push(r);let l=i(u(s,".claude","settings.json"));l!==null&&n.push(l)}let o=t!==void 0?[t]:h();for(let r of o){let l=i(r);l!==null&&n.push(l)}return n}function q(e,s,t=process.platform==="win32"){let n=b(e);for(let i of n)for(let o of s){let r=f(i,o.deny,t);if(r)return{decision:"deny",matchedPattern:r}}for(let i of s){let o=f(e,i.ask,t);if(o)return{decision:"ask",matchedPattern:o};let r=f(e,i.allow,t);if(r)return{decision:"allow",matchedPattern:r}}return{decision:"ask"}}function z(e,s,t=process.platform==="win32"){let n=b(e);for(let i of n)for(let o of s){let r=f(i,o.deny,t);if(r)return{decision:"deny",matchedPattern:r}}return{decision:"allow"}}function I(e,s,t=process.platform==="win32",n){let i=r=>r.replace(/\\/g,"/"),o=new Set;if(o.add(i(e)),n){let r=u(n,e);o.add(i(r));try{o.add(i(R(r)))}catch{}}for(let r of s)for(let l of r){let c=G(i(l),t);for(let g of o)if(c.test(g))return{denied:!0,matchedPattern:l}}return{denied:!1}}var _={python:[/os\.system\(\s*(['"])(.*?)\1\s*\)/g,/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*(['"])(.*?)\1/g],javascript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],typescript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],ruby:[/system\(\s*(['"])(.*?)\1/g,/`(.*?)`/g],go:[/exec\.Command\(\s*(['"`])(.*?)\1/g],php:[/shell_exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])system\(\s*(['"`])(.*?)\1/g,/passthru\(\s*(['"`])(.*?)\1/g,/proc_open\(\s*(['"`])(.*?)\1/g],rust:[/Command::new\(\s*(['"`])(.*?)\1/g]};function F(e){let s=[],t=/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*\[([^\]]+)\]/g,n;for(;(n=t.exec(e))!==null;){let o=[...n[1].matchAll(/(['"])(.*?)\1/g)].map(r=>r[2]);o.length>0&&s.push(o.join(" "))}return s}function H(e,s){let t=_[s];if(!t&&s!=="python")return[];let n=[];if(t)for(let i of t){i.lastIndex=0;let o;for(;(o=i.exec(e))!==null;){let r=o[o.length-1];r&&n.push(r)}}return s==="python"&&n.push(...F(e)),n}export{q as evaluateCommand,z as evaluateCommandDenyOnly,I as evaluateFilePath,H as extractShellCommands,G as fileGlobToRegex,A as globToRegex,f as matchesAnyPattern,w as parseBashPattern,k as parseToolPattern,L as readBashPolicies,M as readToolDenyPatterns,b as splitChainedCommands};
|
|
1
|
+
var h=(n,t)=>()=>(n&&(t=n(n=0)),t);var _,I=h(()=>{"use strict";_={"claude-code":"claude-code","gemini-cli-mcp-client":"gemini-cli","antigravity-client":"antigravity","cursor-vscode":"cursor","Visual-Studio-Code":"vscode-copilot","JetBrains Client":"jetbrains-copilot","IntelliJ IDEA":"jetbrains-copilot",PyCharm:"jetbrains-copilot",Codex:"codex","codex-mcp-client":"codex","Kilo Code":"kilo","Kiro CLI":"kiro","Pi CLI":"pi","Pi Coding Agent":"pi","omp-coding-agent":"omp",Zed:"zed",zed:"zed","qwen-code":"qwen-code","qwen-cli-mcp-client":"qwen-code"}});import{existsSync as l,readFileSync as N}from"node:fs";import{resolve as a}from"node:path";import{homedir as S}from"node:os";function L(){if(f!==null)return f!=="miss"&&f.hasCM;try{let n=a(S(),".claude","plugins","installed_plugins.json"),t=N(n,"utf-8"),e=JSON.parse(t),r=[...Object.keys(e.plugins??{}),...Object.keys(e.enabledPlugins??{})].some(i=>i.includes("context-mode"));return f={hasCM:r},r}catch{return f="miss",!1}}function A(n){switch(n){case"claude-code":return[".claude"];case"gemini-cli":return[".gemini"];case"antigravity":return[".gemini"];case"openclaw":return[".openclaw"];case"codex":return[".codex"];case"cursor":return[".cursor"];case"vscode-copilot":return[".vscode"];case"kiro":return[".kiro"];case"pi":return[".pi"];case"omp":return[".omp"];case"qwen-code":return[".qwen"];case"kilo":return[".config","kilo"];case"opencode":return[".config","opencode"];case"zed":return[".config","zed"];case"jetbrains-copilot":return[".config","JetBrains"];default:return null}}function O(n){if(n?.name){let o=_[n.name];if(o)return{platform:o,confidence:"high",reason:`MCP clientInfo.name="${n.name}"`};if(n.name.startsWith("qwen-cli-mcp-client"))return{platform:"qwen-code",confidence:"high",reason:`MCP clientInfo.name="${n.name}" (qwen-cli pattern)`}}let t=process.env.CONTEXT_MODE_PLATFORM;if(t&&["claude-code","gemini-cli","kilo","opencode","codex","vscode-copilot","jetbrains-copilot","cursor","antigravity","kiro","pi","omp","zed","qwen-code"].includes(t))return{platform:t,confidence:"high",reason:`CONTEXT_MODE_PLATFORM=${t} override`};for(let[o,r]of F)if(r.some(i=>i.detect!==!1&&process.env[i.name]))return o==="vscode-copilot"&&L()?{platform:"claude-code",confidence:"high",reason:"VSCODE_PID set but ~/.claude/plugins/installed_plugins.json lists context-mode (issue #539 fallback)"}:{platform:o,confidence:"high",reason:`${r.filter(i=>i.detect!==!1).map(i=>i.name).join(" or ")} env var set`};let e=S();return l(a(e,".claude"))?{platform:"claude-code",confidence:"medium",reason:"~/.claude/ directory exists"}:l(a(e,".gemini"))?{platform:"gemini-cli",confidence:"medium",reason:"~/.gemini/ directory exists"}:l(a(e,".codex"))?{platform:"codex",confidence:"medium",reason:"~/.codex/ directory exists"}:l(a(e,".kiro"))?{platform:"kiro",confidence:"medium",reason:"~/.kiro/ directory exists"}:l(a(e,".omp"))?{platform:"omp",confidence:"medium",reason:"~/.omp/ directory exists"}:l(a(e,".pi"))?{platform:"pi",confidence:"medium",reason:"~/.pi/ directory exists"}:l(a(e,".qwen"))?{platform:"qwen-code",confidence:"medium",reason:"~/.qwen/ directory exists"}:l(a(e,".openclaw"))?{platform:"openclaw",confidence:"medium",reason:"~/.openclaw/ directory exists"}:l(a(e,".cursor"))?{platform:"cursor",confidence:"medium",reason:"~/.cursor/ directory exists"}:l(a(e,".config","kilo"))?{platform:"kilo",confidence:"medium",reason:"~/.config/kilo/ directory exists"}:l(a(e,".config","JetBrains"))?{platform:"jetbrains-copilot",confidence:"medium",reason:"~/.config/JetBrains/ directory exists"}:l(a(e,".config","opencode"))?{platform:"opencode",confidence:"medium",reason:"~/.config/opencode/ directory exists"}:l(a(e,".config","zed"))?{platform:"zed",confidence:"medium",reason:"~/.config/zed/ directory exists"}:{platform:"claude-code",confidence:"low",reason:"No platform detected, defaulting to Claude Code"}}var f,M,F,R=h(()=>{"use strict";I();f=null;M=[["claude-code",[{name:"CLAUDE_CODE_ENTRYPOINT",role:"identification"},{name:"CLAUDE_PLUGIN_ROOT",role:"identification"},{name:"CLAUDE_PROJECT_DIR",role:"workspace"},{name:"CLAUDE_SESSION_ID",role:"identification"}]],["antigravity",[{name:"ANTIGRAVITY_CLI_ALIAS",role:"identification"}]],["cursor",[{name:"CURSOR_CWD",role:"workspace"},{name:"CURSOR_TRACE_ID",role:"identification"},{name:"CURSOR_CLI",role:"identification"}]],["kilo",[{name:"KILO",role:"identification"},{name:"KILO_PID",role:"identification"}]],["opencode",[{name:"OPENCODE_PROJECT_DIR",role:"workspace"},{name:"OPENCODE_CLIENT",role:"identification"},{name:"OPENCODE_TERMINAL",role:"identification"},{name:"OPENCODE",role:"identification"},{name:"OPENCODE_PID",role:"identification"}]],["zed",[{name:"ZED_SESSION_ID",role:"identification"},{name:"ZED_TERM",role:"identification"}]],["codex",[{name:"CODEX_THREAD_ID",role:"identification"},{name:"CODEX_CI",role:"identification"}]],["gemini-cli",[{name:"GEMINI_PROJECT_DIR",role:"workspace"},{name:"GEMINI_CLI",role:"identification"}]],["vscode-copilot",[{name:"VSCODE_CWD",role:"workspace"},{name:"VSCODE_PID",role:"identification"}]],["jetbrains-copilot",[{name:"IDEA_INITIAL_DIRECTORY",role:"workspace"}]],["qwen-code",[{name:"QWEN_PROJECT_DIR",role:"workspace"}]],["omp",[{name:"PI_CODING_AGENT_DIR",role:"workspace"}]],["pi",[{name:"PI_WORKSPACE_DIR",role:"workspace",detect:!1},{name:"PI_PROJECT_DIR",role:"workspace",detect:!1},{name:"PI_CONFIG_DIR",role:"identification"},{name:"PI_SESSION_FILE",role:"identification"},{name:"PI_COMPILED",role:"identification"}]]],F=new Map(M)});import{resolve as p}from"node:path";import{homedir as P}from"node:os";function j(n=process.env){let t=n.CLAUDE_CONFIG_DIR;return t&&t.trim()!==""?t.startsWith("~")?p(P(),t.replace(/^~[/\\]?/,"")):p(t):p(P(),".claude")}function G(n=process.env){return p(j(n),"settings.json")}function w(n=process.env){let t=[],e=O();if(e.platform!=="claude-code"){let r=A(e.platform);r&&r.length>0&&t.push(p(P(),...r,"settings.json"))}let o=G(n);return t.includes(o)||t.push(o),t}var v=h(()=>{"use strict";R()});v();import{readFileSync as D,realpathSync as $}from"node:fs";import{resolve as u}from"node:path";function b(n){let t=n.match(/^Bash\((.+)\)$/);return t?t[1]:null}function J(n){let t=n.match(/^(\w+)\((.+)\)$/);return t?{tool:t[1],glob:t[2]}:null}function q(n){return n.replace(/[.*+?^${}()|[\]\\\/\-]/g,"\\$&")}function k(n){return n.replace(/[.+?^${}()|[\]\\\/\-]/g,"\\$&").replace(/\*/g,".*")}function V(n,t=!1){let e,o=n.indexOf(":");if(o!==-1){let r=n.slice(0,o),i=n.slice(o+1),s=q(r),c=k(i);e=`^${s}(\\s${c})?$`}else e=`^${k(n)}$`;return new RegExp(e,t?"i":"")}function z(n,t=!1){let e="",o=0;for(;o<n.length;)n[o]==="*"&&n[o+1]==="*"?o+2<n.length&&n[o+2]==="/"?(e+="(.*/)?",o+=3):(e+=".*",o+=2):n[o]==="*"?(e+="[^/]*",o++):n[o]==="?"?(e+="[^/]",o++):(e+=n[o].replace(/[.+^${}()|[\]\\\/\-]/g,"\\$&"),o++);return new RegExp(`^${e}$`,t?"i":"")}function g(n,t,e=!1){for(let o of t){let r=b(o);if(r&&V(r,e).test(n))return o}return null}function T(n){let t=[],e="",o=!1,r=!1,i=!1;for(let s=0;s<n.length;s++){let c=n[s],d=s>0?n[s-1]:"";c==="'"&&!r&&!i&&d!=="\\"?(o=!o,e+=c):c==='"'&&!o&&!i&&d!=="\\"?(r=!r,e+=c):c==="`"&&!o&&!r&&d!=="\\"?(i=!i,e+=c):!o&&!r&&!i?c===";"?(t.push(e.trim()),e=""):c==="|"&&n[s+1]==="|"||c==="&"&&n[s+1]==="&"?(t.push(e.trim()),e="",s++):c==="|"?(t.push(e.trim()),e=""):e+=c:e+=c}return e.trim()&&t.push(e.trim()),t.filter(s=>s.length>0)}function C(n){let t;try{t=D(n,"utf-8")}catch{return null}let e;try{e=JSON.parse(t)}catch{return null}let o=e?.permissions;if(!o||typeof o!="object")return null;let r=i=>Array.isArray(i)?i.filter(s=>typeof s=="string"&&b(s)!==null):[];return{allow:r(o.allow),deny:r(o.deny),ask:r(o.ask)}}function ce(n,t){let e=[];if(n){let r=u(n,".claude","settings.local.json"),i=C(r);i&&e.push(i);let s=u(n,".claude","settings.json"),c=C(s);c&&e.push(c)}let o=t!==void 0?[t]:w();for(let r of o){let i=C(r);i&&e.push(i)}return e}function ae(n,t,e){let o=[],r=s=>{let c;try{c=D(s,"utf-8")}catch{return null}let d;try{d=JSON.parse(c)}catch{return null}let m=d?.permissions?.deny;if(!Array.isArray(m))return[];let x=[];for(let E of m){if(typeof E!="string")continue;let y=J(E);y&&y.tool===n&&x.push(y.glob)}return x};if(t){let s=r(u(t,".claude","settings.local.json"));s!==null&&o.push(s);let c=r(u(t,".claude","settings.json"));c!==null&&o.push(c)}let i=e!==void 0?[e]:w();for(let s of i){let c=r(s);c!==null&&o.push(c)}return o}function le(n,t,e=process.platform==="win32"){let o=T(n);for(let r of o)for(let i of t){let s=g(r,i.deny,e);if(s)return{decision:"deny",matchedPattern:s}}for(let r of t){let i=g(n,r.ask,e);if(i)return{decision:"ask",matchedPattern:i};let s=g(n,r.allow,e);if(s)return{decision:"allow",matchedPattern:s}}return{decision:"ask"}}function de(n,t,e=process.platform==="win32"){let o=T(n);for(let r of o)for(let i of t){let s=g(r,i.deny,e);if(s)return{decision:"deny",matchedPattern:s}}return{decision:"allow"}}function fe(n,t,e=process.platform==="win32",o){let r=s=>s.replace(/\\/g,"/"),i=new Set;if(i.add(r(n)),o){let s=u(o,n);i.add(r(s));try{i.add(r($(s)))}catch{}}for(let s of t)for(let c of s){let d=z(r(c),e);for(let m of i)if(d.test(m))return{denied:!0,matchedPattern:c}}return{denied:!1}}var B={python:[/os\.system\(\s*(['"])(.*?)\1\s*\)/g,/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*(['"])(.*?)\1/g],javascript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],typescript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],ruby:[/system\(\s*(['"])(.*?)\1/g,/`(.*?)`/g],go:[/exec\.Command\(\s*(['"`])(.*?)\1/g],php:[/shell_exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])system\(\s*(['"`])(.*?)\1/g,/passthru\(\s*(['"`])(.*?)\1/g,/proc_open\(\s*(['"`])(.*?)\1/g],rust:[/Command::new\(\s*(['"`])(.*?)\1/g]};function U(n){let t=[],e=/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*\[([^\]]+)\]/g,o;for(;(o=e.exec(n))!==null;){let i=[...o[1].matchAll(/(['"])(.*?)\1/g)].map(s=>s[2]);i.length>0&&t.push(i.join(" "))}return t}function pe(n,t){let e=B[t];if(!e&&t!=="python")return[];let o=[];if(e)for(let r of e){r.lastIndex=0;let i;for(;(i=r.exec(n))!==null;){let s=i[i.length-1];s&&o.push(s)}}return t==="python"&&o.push(...U(n)),o}export{le as evaluateCommand,de as evaluateCommandDenyOnly,fe as evaluateFilePath,pe as extractShellCommands,z as fileGlobToRegex,V as globToRegex,g as matchesAnyPattern,b as parseBashPattern,J as parseToolPattern,ce as readBashPolicies,ae as readToolDenyPatterns,T as splitChainedCommands};
|
|
@@ -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",
|