@mxalbert/context-mode 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +1 -1
- package/build/adapters/opencode/plugin.d.ts +60 -2
- package/build/adapters/opencode/plugin.js +89 -1
- package/build/adapters/opencode/v2.d.ts +11 -0
- package/build/server.js +98 -46
- package/build/session/purge.js +2 -1
- package/build/store.d.ts +22 -3
- package/build/store.js +32 -34
- package/cli.bundle.mjs +149 -149
- package/configs/antigravity-cli/plugin.json +1 -1
- package/configs/copilot-cli/.github/plugin/plugin.json +1 -1
- package/hooks/core/routing.mjs +168 -5
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +126 -126
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "context-mode for Antigravity CLI (agy): sandboxed code execution, FTS5 knowledge base, and session capture. Saves your context window by keeping raw bytes out of the conversation.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
3
|
"description": "context-mode for GitHub Copilot CLI: sandboxed code execution in 11 languages, an FTS5 knowledge base with BM25 ranking, and session capture. Saves your context window by keeping raw bytes out of the conversation.",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.3",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
7
7
|
"context-window",
|
package/hooks/core/routing.mjs
CHANGED
|
@@ -685,6 +685,52 @@ function getPlatformSettingsPath(platform) {
|
|
|
685
685
|
return undefined;
|
|
686
686
|
}
|
|
687
687
|
|
|
688
|
+
/**
|
|
689
|
+
* Platforms whose PreToolUse response can surface a real "ask" confirmation
|
|
690
|
+
* prompt to the user (hooks/core/formatters.mjs emits permissionDecision:"ask"
|
|
691
|
+
* or the platform equivalent).
|
|
692
|
+
*
|
|
693
|
+
* Everywhere else: an `ask` match on an ordinary Bash command is NOT returned
|
|
694
|
+
* from routePreToolUse (see the Stage-1 comment inside — the host's own
|
|
695
|
+
* permission system decides), while the ctx_* sandbox branches deny with an
|
|
696
|
+
* actionable reason via sandboxAskDecision — the gemini-cli/codex/kimi/kiro
|
|
697
|
+
* formatters silently DROP ask, which would fail the sandbox open. Keep in
|
|
698
|
+
* sync with the per-platform `ask` formatters; claude-code also covers
|
|
699
|
+
* qwen-code, which shares the claude-code hook script and wire protocol
|
|
700
|
+
* (src/cli.ts HOOK_MAP).
|
|
701
|
+
*/
|
|
702
|
+
const ASK_CAPABLE_PLATFORMS = new Set([
|
|
703
|
+
"claude-code", // + qwen-code (shared hook script / wire protocol)
|
|
704
|
+
"vscode-copilot",
|
|
705
|
+
"jetbrains-copilot",
|
|
706
|
+
"copilot-cli",
|
|
707
|
+
"cursor",
|
|
708
|
+
"antigravity-cli",
|
|
709
|
+
]);
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Resolve an `ask` policy match inside the ctx_* sandbox tools for platforms
|
|
713
|
+
* with no confirmation surface. Unlike ordinary Bash calls — where the host's
|
|
714
|
+
* own permission system backs the decision up — nothing re-gates sandbox
|
|
715
|
+
* execution, and several formatters (gemini-cli, codex, kimi return null;
|
|
716
|
+
* kiro exits 0) would silently drop an `ask` decision and run the code
|
|
717
|
+
* unconfirmed. Deny with an actionable reason instead: fail-closed,
|
|
718
|
+
* consistent with the plugin adapters (OpenCode/KiloCode/OpenClaw) that
|
|
719
|
+
* block on ask.
|
|
720
|
+
*/
|
|
721
|
+
function sandboxAskDecision(platform, describe, matchedPattern) {
|
|
722
|
+
if (ASK_CAPABLE_PLATFORMS.has(platform || "claude-code")) {
|
|
723
|
+
return { action: "ask" };
|
|
724
|
+
}
|
|
725
|
+
return {
|
|
726
|
+
action: "deny",
|
|
727
|
+
reason:
|
|
728
|
+
`Blocked by security policy: ${describe} matches ask pattern ${matchedPattern} — ` +
|
|
729
|
+
"this platform has no interactive confirmation path for sandbox execution. " +
|
|
730
|
+
"Move the pattern to allow (or deny) in your settings to make the decision explicit.",
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
688
734
|
/**
|
|
689
735
|
* Route a PreToolUse event. Returns normalized decision object or null for passthrough.
|
|
690
736
|
*
|
|
@@ -749,9 +795,25 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
749
795
|
return { action: "deny", reason: `Blocked by security policy: matches deny pattern ${result.matchedPattern}` };
|
|
750
796
|
}
|
|
751
797
|
if (result.decision === "ask" && result.matchedPattern) {
|
|
752
|
-
|
|
798
|
+
// An ask pattern expresses "confirm with me" — a prompt, not a
|
|
799
|
+
// block. Only surface the ask decision on platforms whose
|
|
800
|
+
// PreToolUse response can render a real confirmation prompt
|
|
801
|
+
// (ASK_CAPABLE_PLATFORMS). Elsewhere the decision degraded into
|
|
802
|
+
// a hard block — the OpenCode/KiloCode plugins throw and OpenClaw
|
|
803
|
+
// blocks on ask — silently converting a confirmation intent into
|
|
804
|
+
// a denial: `Bash(git commit:*)` ask-listed for Claude Code's
|
|
805
|
+
// prompt hard-blocked `git commit` on OpenCode with no way to
|
|
806
|
+
// proceed. Fall through to Stage 2 and let the host's own
|
|
807
|
+
// permission system decide. The ctx_* sandbox branches below
|
|
808
|
+
// keep ask enforcement fail-safe instead — prompt where the
|
|
809
|
+
// platform can render one, deny with an actionable reason
|
|
810
|
+
// elsewhere — because no host permission system backs those
|
|
811
|
+
// calls.
|
|
812
|
+
if (ASK_CAPABLE_PLATFORMS.has(platform || "claude-code")) {
|
|
813
|
+
return { action: "ask" };
|
|
814
|
+
}
|
|
753
815
|
}
|
|
754
|
-
// "allow" or no match → fall through to Stage 2
|
|
816
|
+
// "allow" or no match (or ask on a prompt-incapable platform) → fall through to Stage 2
|
|
755
817
|
}
|
|
756
818
|
}
|
|
757
819
|
|
|
@@ -965,7 +1027,7 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
965
1027
|
return { action: "deny", reason: `Blocked by security policy: shell code matches deny pattern ${result.matchedPattern}` };
|
|
966
1028
|
}
|
|
967
1029
|
if (result.decision === "ask" && result.matchedPattern) {
|
|
968
|
-
return
|
|
1030
|
+
return sandboxAskDecision(platform, "shell code", result.matchedPattern);
|
|
969
1031
|
}
|
|
970
1032
|
}
|
|
971
1033
|
}
|
|
@@ -997,7 +1059,7 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
997
1059
|
return { action: "deny", reason: `Blocked by security policy: shell code matches deny pattern ${result.matchedPattern}` };
|
|
998
1060
|
}
|
|
999
1061
|
if (result.decision === "ask" && result.matchedPattern) {
|
|
1000
|
-
return
|
|
1062
|
+
return sandboxAskDecision(platform, "shell code", result.matchedPattern);
|
|
1001
1063
|
}
|
|
1002
1064
|
}
|
|
1003
1065
|
}
|
|
@@ -1018,7 +1080,7 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
1018
1080
|
return { action: "deny", reason: `Blocked by security policy: batch command "${entry.label ?? cmd}" matches deny pattern ${result.matchedPattern}` };
|
|
1019
1081
|
}
|
|
1020
1082
|
if (result.decision === "ask" && result.matchedPattern) {
|
|
1021
|
-
return {
|
|
1083
|
+
return sandboxAskDecision(platform, `batch command "${entry.label ?? cmd}"`, result.matchedPattern);
|
|
1022
1084
|
}
|
|
1023
1085
|
}
|
|
1024
1086
|
}
|
|
@@ -1048,3 +1110,104 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessi
|
|
|
1048
1110
|
// Unknown tool — pass through
|
|
1049
1111
|
return null;
|
|
1050
1112
|
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Route an OpenCode v2 permission "evaluate" event (ctx.permission.hook).
|
|
1116
|
+
*
|
|
1117
|
+
* v2's permission system asserts every core-tool action before execution and
|
|
1118
|
+
* lets plugins mutate the decision: `event.effect` ("allow" | "deny" | "ask")
|
|
1119
|
+
* and `event.message`. This maps context-mode's security policies onto that
|
|
1120
|
+
* surface, restoring ask/confirmation semantics that the execute.before bridge
|
|
1121
|
+
* cannot express (an ask there can only throw = hard block):
|
|
1122
|
+
*
|
|
1123
|
+
* - deny match → { effect: "deny" } — blocked with a reason. Normally
|
|
1124
|
+
* pre-empted by the execute.before throw (which fires first and is
|
|
1125
|
+
* unaffected by --auto); kept as defense-in-depth for any assert path
|
|
1126
|
+
* execute.before misses.
|
|
1127
|
+
* - ask match → { effect: "ask" } — an interactive confirmation in
|
|
1128
|
+
* TUI runs, restoring the user's ask intent even when host allow rules
|
|
1129
|
+
* would have auto-approved (verified live: a project-tier allow cannot
|
|
1130
|
+
* mask a global-tier ask). Under `--auto` the host auto-approves the
|
|
1131
|
+
* resulting ask too — an explicit opt-in (verified live).
|
|
1132
|
+
* - allow match → { effect: "allow" } — skips the host's default-ask
|
|
1133
|
+
* prompt for commands the user's own global rules pre-approve.
|
|
1134
|
+
* - no match → null — NO OPINION: the host decision (its own rules,
|
|
1135
|
+
* default ask, or --auto) stays untouched.
|
|
1136
|
+
*
|
|
1137
|
+
* Asymmetry is deliberate: deny/ask are honored from ALL policy tiers
|
|
1138
|
+
* (project + global — enforcement direction), while allow is honored from
|
|
1139
|
+
* GLOBAL tiers only. A project-shipped .claude/settings.json can harden a
|
|
1140
|
+
* repo's agent (deny/ask) but must not be able to weaken the host's
|
|
1141
|
+
* confirmation by pre-approving commands in a freshly cloned repo.
|
|
1142
|
+
*
|
|
1143
|
+
* Only the v2 "shell" action carries Bash-policy semantics — resources are the
|
|
1144
|
+
* parsed command statements of one shell invocation. Other actions (read,
|
|
1145
|
+
* write, edit, …) have no context-mode policy mapping (routePreToolUse has no
|
|
1146
|
+
* security branch for them either — parity) and return null.
|
|
1147
|
+
*
|
|
1148
|
+
* Fail-open on a missing security module, mirroring routePreToolUse Stage 1.
|
|
1149
|
+
*
|
|
1150
|
+
* @param {string} action - v2 permission action name ("shell" for the shell tool)
|
|
1151
|
+
* @param {string[]} resources - parsed command statement strings
|
|
1152
|
+
* @param {string} [projectDir] - project directory for policy lookup
|
|
1153
|
+
* @param {string} [platform] - platform ID for the adapter settings path
|
|
1154
|
+
* @returns {{ effect: "allow" | "deny" | "ask", message?: string } | null}
|
|
1155
|
+
*/
|
|
1156
|
+
export function routePermissionEvaluate(action, resources, projectDir, platform) {
|
|
1157
|
+
if (action !== "shell") return null;
|
|
1158
|
+
if (!Array.isArray(resources) || resources.length === 0) return null;
|
|
1159
|
+
if (!security) return null;
|
|
1160
|
+
|
|
1161
|
+
const platformSettingsPath = getPlatformSettingsPath(platform);
|
|
1162
|
+
// Full policies (project + global) drive deny/ask — enforcement direction.
|
|
1163
|
+
const policies = security.readBashPolicies(projectDir, platformSettingsPath);
|
|
1164
|
+
if (policies.length === 0) return null;
|
|
1165
|
+
|
|
1166
|
+
const commands = resources.filter((r) => typeof r === "string" && r.length > 0);
|
|
1167
|
+
if (commands.length === 0) return null;
|
|
1168
|
+
|
|
1169
|
+
const results = commands.map((command) => security.evaluateCommand(command, policies));
|
|
1170
|
+
|
|
1171
|
+
// Deny wins across all statements (chain semantics: one denied segment
|
|
1172
|
+
// blocks the whole invocation — same as evaluateCommand).
|
|
1173
|
+
const denied = results.find((r) => r.decision === "deny");
|
|
1174
|
+
if (denied) {
|
|
1175
|
+
return {
|
|
1176
|
+
effect: "deny",
|
|
1177
|
+
message: `Blocked by security policy: matches deny pattern ${denied.matchedPattern}`,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Ask: evaluated per tier INDEPENDENTLY. evaluateCommand on the combined
|
|
1182
|
+
// policy list returns the FIRST definitive result, so a project-tier allow
|
|
1183
|
+
// would mask a later global-tier ask — and since OpenCode v2's default
|
|
1184
|
+
// shell permission is allow, the user's global ask would silently vanish.
|
|
1185
|
+
// Per-policy calls keep evaluateCommand's chain/subshell parsing while
|
|
1186
|
+
// honoring an explicit ask from EVERY tier.
|
|
1187
|
+
for (const command of commands) {
|
|
1188
|
+
for (const policy of policies) {
|
|
1189
|
+
const result = security.evaluateCommand(command, [policy]);
|
|
1190
|
+
if (result.decision === "ask" && result.matchedPattern) {
|
|
1191
|
+
return {
|
|
1192
|
+
effect: "ask",
|
|
1193
|
+
message: `Confirmation required: matches ask pattern ${result.matchedPattern}`,
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Allow: global tiers only (the user's own files) — a project cannot
|
|
1200
|
+
// pre-approve. All statements must be explicitly allowed, mirroring
|
|
1201
|
+
// evaluateCommand's "allowed iff every segment explicitly allowed".
|
|
1202
|
+
const globalPolicies = security.readBashPolicies(undefined, platformSettingsPath);
|
|
1203
|
+
if (globalPolicies.length > 0) {
|
|
1204
|
+
const allAllowed = commands.every((command) => {
|
|
1205
|
+
const result = security.evaluateCommand(command, globalPolicies);
|
|
1206
|
+
return result.decision === "allow" && result.matchedPattern;
|
|
1207
|
+
});
|
|
1208
|
+
if (allAllowed) return { effect: "allow" };
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// No explicit match — no opinion; the host decides.
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
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": "2.0.
|
|
6
|
+
"version": "2.0.3",
|
|
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": "@mxalbert/context-mode",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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",
|