context-mode 1.0.162 → 1.0.163
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 +142 -28
- package/bin/statusline.mjs +24 -4
- package/build/adapters/antigravity/index.d.ts +1 -1
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +341 -0
- package/build/adapters/claude-code/hooks.d.ts +1 -0
- package/build/adapters/claude-code/hooks.js +3 -0
- package/build/adapters/claude-code/index.js +24 -5
- package/build/adapters/client-map.js +5 -0
- package/build/adapters/codex/hooks.d.ts +5 -1
- package/build/adapters/codex/hooks.js +5 -1
- package/build/adapters/codex/index.d.ts +9 -1
- package/build/adapters/codex/index.js +87 -5
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/detect.d.ts +1 -1
- package/build/adapters/detect.js +71 -3
- package/build/adapters/openclaw/mcp-tools.js +1 -1
- package/build/adapters/opencode/index.js +31 -17
- package/build/adapters/opencode/zod3tov4.js +27 -6
- package/build/adapters/pi/extension.d.ts +2 -12
- package/build/adapters/pi/extension.js +114 -96
- package/build/adapters/types.d.ts +5 -4
- package/build/adapters/types.js +4 -3
- package/build/cache-heal.d.ts +48 -0
- package/build/cache-heal.js +150 -0
- package/build/cli.js +37 -97
- package/build/executor.d.ts +25 -0
- package/build/executor.js +143 -22
- package/build/opencode-plugin.js +5 -2
- package/build/routing-block.d.ts +8 -0
- package/build/routing-block.js +86 -0
- package/build/runtime.d.ts +0 -36
- package/build/runtime.js +107 -27
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/security.d.ts +8 -3
- package/build/security.js +155 -29
- package/build/server.d.ts +14 -0
- package/build/server.js +368 -350
- package/build/session/analytics.d.ts +1 -1
- package/build/session/analytics.js +5 -1
- package/build/session/db.js +23 -3
- package/build/session/extract.js +8 -0
- package/build/store.d.ts +1 -1
- package/build/store.js +139 -25
- package/build/tool-naming.d.ts +4 -0
- package/build/tool-naming.js +24 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/cli.bundle.mjs +254 -252
- package/configs/antigravity/GEMINI.md +2 -2
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +2 -2
- package/configs/codex/AGENTS.md +2 -2
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/gemini-cli/GEMINI.md +2 -2
- package/configs/jetbrains-copilot/copilot-instructions.md +2 -2
- package/configs/kilo/AGENTS.md +2 -2
- package/configs/kiro/KIRO.md +2 -2
- package/configs/omp/SYSTEM.md +2 -2
- package/configs/openclaw/AGENTS.md +2 -2
- package/configs/opencode/AGENTS.md +2 -2
- package/configs/qwen-code/QWEN.md +2 -2
- package/configs/vscode-copilot/copilot-instructions.md +2 -2
- package/configs/zed/AGENTS.md +2 -2
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/codex/pretooluse.mjs +14 -4
- package/hooks/codex/stop.mjs +12 -4
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +158 -7
- package/hooks/core/mcp-ready.mjs +37 -8
- package/hooks/core/routing.mjs +94 -8
- package/hooks/core/tool-naming.mjs +3 -0
- package/hooks/hooks.json +12 -1
- package/hooks/pretooluse.mjs +6 -2
- package/hooks/routing-block.mjs +2 -2
- package/hooks/security.bundle.mjs +2 -1
- package/hooks/session-db.bundle.mjs +5 -5
- package/hooks/session-directive.mjs +88 -20
- package/hooks/session-extract.bundle.mjs +1 -1
- package/hooks/session-helpers.mjs +21 -0
- package/hooks/sessionstart.mjs +37 -5
- package/hooks/stop.mjs +49 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -10
- package/scripts/install-antigravity-cli-plugin.mjs +141 -0
- package/server.bundle.mjs +208 -203
- package/skills/ctx-insight/SKILL.md +12 -17
- package/build/util/db-lock.d.ts +0 -65
- package/build/util/db-lock.js +0 -166
- package/insight/index.html +0 -13
- package/insight/package.json +0 -55
- package/insight/server.mjs +0 -1265
- package/insight/src/components/analytics.tsx +0 -112
- package/insight/src/components/ui/badge.tsx +0 -52
- package/insight/src/components/ui/button.tsx +0 -58
- package/insight/src/components/ui/card.tsx +0 -103
- package/insight/src/components/ui/chart.tsx +0 -371
- package/insight/src/components/ui/collapsible.tsx +0 -19
- package/insight/src/components/ui/input.tsx +0 -20
- package/insight/src/components/ui/progress.tsx +0 -83
- package/insight/src/components/ui/scroll-area.tsx +0 -55
- package/insight/src/components/ui/separator.tsx +0 -23
- package/insight/src/components/ui/table.tsx +0 -114
- package/insight/src/components/ui/tabs.tsx +0 -82
- package/insight/src/components/ui/tooltip.tsx +0 -64
- package/insight/src/lib/api.ts +0 -144
- package/insight/src/lib/utils.ts +0 -6
- package/insight/src/main.tsx +0 -22
- package/insight/src/routeTree.gen.ts +0 -189
- package/insight/src/router.tsx +0 -19
- package/insight/src/routes/__root.tsx +0 -55
- package/insight/src/routes/enterprise.tsx +0 -316
- package/insight/src/routes/index.tsx +0 -1482
- package/insight/src/routes/knowledge.tsx +0 -221
- package/insight/src/routes/knowledge_.$dbHash.$sourceId.tsx +0 -137
- package/insight/src/routes/search.tsx +0 -97
- package/insight/src/routes/sessions.tsx +0 -179
- package/insight/src/routes/sessions_.$dbHash.$sessionId.tsx +0 -181
- package/insight/src/styles.css +0 -104
- package/insight/tsconfig.json +0 -29
- package/insight/vite.config.ts +0 -19
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export function createRoutingBlock(t, options = {}) {
|
|
2
|
+
const { includeCommands = true } = options;
|
|
3
|
+
return `
|
|
4
|
+
<context_window_protection>
|
|
5
|
+
<priority_instructions>
|
|
6
|
+
Raw tool output floods context window. MUST use context-mode MCP tools. Keep raw data in sandbox.
|
|
7
|
+
</priority_instructions>
|
|
8
|
+
|
|
9
|
+
<tool_selection_hierarchy>
|
|
10
|
+
0. MEMORY: ${t("ctx_search")}(sort: "timeline")
|
|
11
|
+
- After resume, check prior context before asking user.
|
|
12
|
+
1. GATHER: ${t("ctx_batch_execute")}(commands, queries)
|
|
13
|
+
- Primary research tool. Runs commands, auto-indexes, searches. ONE call replaces many steps.
|
|
14
|
+
- Each command: {label: "section header", command: "shell command"}
|
|
15
|
+
- label becomes FTS5 chunk title — descriptive labels improve search.
|
|
16
|
+
2. FOLLOW-UP: ${t("ctx_search")}(queries: ["q1", "q2", ...])
|
|
17
|
+
- All follow-up questions. ONE call, many queries (default relevance mode).
|
|
18
|
+
3. PROCESSING: ${t("ctx_execute")}(language, code) | ${t("ctx_execute_file")}(path, language, code)
|
|
19
|
+
- API calls, log analysis, data processing.
|
|
20
|
+
</tool_selection_hierarchy>
|
|
21
|
+
|
|
22
|
+
<forbidden_actions>
|
|
23
|
+
- NO Bash for commands producing >20 lines output.
|
|
24
|
+
- NO Read for analysis — use execute_file. Read IS correct for files you intend to Edit.
|
|
25
|
+
- NO WebFetch — use ${t("ctx_fetch_and_index")}.
|
|
26
|
+
- Bash ONLY for git/mkdir/rm/mv/navigation.
|
|
27
|
+
- NO ${t("ctx_execute")} or ${t("ctx_execute_file")} for file creation/modification.
|
|
28
|
+
ctx_execute is for analysis, processing, computation only.
|
|
29
|
+
</forbidden_actions>
|
|
30
|
+
|
|
31
|
+
<file_writing_policy>
|
|
32
|
+
ALWAYS use native Write/Edit tools for file creation/modification.
|
|
33
|
+
NEVER use ${t("ctx_execute")}, ${t("ctx_execute_file")}, or Bash to write files.
|
|
34
|
+
Applies to all file types: code, configs, plans, specs, YAML, JSON, markdown.
|
|
35
|
+
</file_writing_policy>
|
|
36
|
+
|
|
37
|
+
<output_constraints>
|
|
38
|
+
<communication_style>
|
|
39
|
+
Terse like caveman. Technical substance exact. Only fluff die.
|
|
40
|
+
Use fragments when clear. Short synonyms (fix not "implement a solution for").
|
|
41
|
+
Technical terms exact. Code blocks unchanged.
|
|
42
|
+
Auto-expand for: security warnings, irreversible actions, user confusion.
|
|
43
|
+
</communication_style>
|
|
44
|
+
<artifact_policy>
|
|
45
|
+
Write artifacts (code, configs, PRDs) to FILES. NEVER inline.
|
|
46
|
+
Return only: file path + 1-line description.
|
|
47
|
+
</artifact_policy>
|
|
48
|
+
<response_format>
|
|
49
|
+
Concise summary:
|
|
50
|
+
- Actions taken (2-3 bullets)
|
|
51
|
+
- File paths created/modified
|
|
52
|
+
- Key findings
|
|
53
|
+
</response_format>
|
|
54
|
+
</output_constraints>
|
|
55
|
+
<session_continuity>
|
|
56
|
+
Skills, roles, and decisions set during this session remain active until the user revokes them.
|
|
57
|
+
Do not drop behavioral directives as context grows.
|
|
58
|
+
</session_continuity>
|
|
59
|
+
${includeCommands ? `
|
|
60
|
+
<ctx_commands>
|
|
61
|
+
"ctx stats" | "ctx-stats" | "/ctx-stats" | context savings question
|
|
62
|
+
→ Call stats MCP tool, display full output verbatim.
|
|
63
|
+
|
|
64
|
+
"ctx doctor" | "ctx-doctor" | "/ctx-doctor" | diagnose context-mode
|
|
65
|
+
→ Call doctor MCP tool, run returned shell command, display as checklist.
|
|
66
|
+
|
|
67
|
+
"ctx upgrade" | "ctx-upgrade" | "/ctx-upgrade" | update context-mode
|
|
68
|
+
→ Call upgrade MCP tool, run returned shell command, display as checklist.
|
|
69
|
+
|
|
70
|
+
"ctx purge" | "ctx-purge" | "/ctx-purge" | wipe/reset knowledge base
|
|
71
|
+
→ Call purge MCP tool with confirm: true. Warn: irreversible.
|
|
72
|
+
|
|
73
|
+
After /clear or /compact: knowledge base preserved. Tell user: "context-mode knowledge base preserved. Use \`ctx purge\` to start fresh."
|
|
74
|
+
</ctx_commands>
|
|
75
|
+
` : ''}
|
|
76
|
+
</context_window_protection>`;
|
|
77
|
+
}
|
|
78
|
+
export function createReadGuidance(t) {
|
|
79
|
+
return '<context_guidance>\n <tip>\n Reading to Edit? Read is correct — Edit needs content in context.\n Reading to analyze/explore? Use ' + t("ctx_execute_file") + '(path, language, code) — only printed summary enters context.\n </tip>\n</context_guidance>';
|
|
80
|
+
}
|
|
81
|
+
export function createGrepGuidance(t) {
|
|
82
|
+
return '<context_guidance>\n <tip>\n May flood context. Use ' + t("ctx_execute") + '(language: "shell", code: "...") to run searches in sandbox. Only printed summary enters context.\n </tip>\n</context_guidance>';
|
|
83
|
+
}
|
|
84
|
+
export function createBashGuidance(t) {
|
|
85
|
+
return '<context_guidance>\n <tip>\n May produce large output. Use ' + t("ctx_batch_execute") + '(commands, queries) for multiple commands, ' + t("ctx_execute") + '(language: "shell", code: "...") for single. Only printed summary enters context. Bash only for: git, mkdir, rm, mv, navigation.\n </tip>\n</context_guidance>';
|
|
86
|
+
}
|
package/build/runtime.d.ts
CHANGED
|
@@ -65,42 +65,6 @@ export interface HookRuntime {
|
|
|
65
65
|
* mask the mock and yield the host's real bun/node detection result.
|
|
66
66
|
*/
|
|
67
67
|
export declare function resetHookRuntimeCache(): void;
|
|
68
|
-
/**
|
|
69
|
-
* Resolve the JS runtime to use for spawning hook scripts (issue #738).
|
|
70
|
-
*
|
|
71
|
-
* Returns Bun when:
|
|
72
|
-
* - a bun binary is located via {@link bunCommand} (already handles the
|
|
73
|
-
* Windows .cmd shim trap from #506 + absolute path fallbacks), AND
|
|
74
|
-
* - `bun --version` exits 0 within the probe timeout, AND
|
|
75
|
-
* - the reported semver major is ≥1.
|
|
76
|
-
*
|
|
77
|
-
* Returns Node (`process.execPath`) on every other path — missing bun,
|
|
78
|
-
* version probe failure, version <1, malformed version banner. Silent
|
|
79
|
-
* fallback: never throws, never logs to stderr (a noisy log would clutter
|
|
80
|
-
* the same MCP boot output that #719 tightened up).
|
|
81
|
-
*
|
|
82
|
-
* Result is cached at module load so the cost is amortised across every
|
|
83
|
-
* hook command emission for the lifetime of the process. The cache also
|
|
84
|
-
* keeps the behaviour deterministic — if the user `brew uninstall bun`
|
|
85
|
-
* mid-session, the cached resolution stays valid for that session and the
|
|
86
|
-
* next MCP boot re-detects.
|
|
87
|
-
*
|
|
88
|
-
* Why bun ≥1.0 instead of "any bun":
|
|
89
|
-
* - Bun 0.x had multiple ESM/module-resolution regressions that broke
|
|
90
|
-
* dynamic `import()` inside hooks (and our hooks do ~7 dynamic imports
|
|
91
|
-
* in `pretooluse.mjs`).
|
|
92
|
-
* - 1.0 ships stable npm-compat that our better-sqlite3-adjacent code
|
|
93
|
-
* relies on indirectly (hooks share `ensure-deps.mjs` which is
|
|
94
|
-
* bun-safe past 1.0 but not 0.x).
|
|
95
|
-
*
|
|
96
|
-
* NOT used by:
|
|
97
|
-
* - `buildNodeCommand` — kept on `process.execPath` for openclaw doctor /
|
|
98
|
-
* upgrade hints which must invoke the better-sqlite3-loading CLI on
|
|
99
|
-
* Node (#543: bun cannot dlopen better-sqlite3's prebuilt .node).
|
|
100
|
-
* - `ensure-deps.mjs` — separate path, must stay on Node for the same
|
|
101
|
-
* reason.
|
|
102
|
-
* - `ctx_upgrade` — separate path, must stay on Node for the same reason.
|
|
103
|
-
*/
|
|
104
68
|
export declare function resolveHookRuntime(): HookRuntime;
|
|
105
69
|
export declare function getRuntimeSummary(runtimes: RuntimeMap): string;
|
|
106
70
|
export declare function getAvailableLanguages(runtimes: RuntimeMap): Language[];
|
package/build/runtime.js
CHANGED
|
@@ -27,6 +27,10 @@ function isWindowsWslBash(shellPath) {
|
|
|
27
27
|
return /\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(lower) ||
|
|
28
28
|
/\\microsoft\\windowsapps\\bash\.exe$/.test(lower);
|
|
29
29
|
}
|
|
30
|
+
function isWindowsSystemCmd(shellPath) {
|
|
31
|
+
const lower = shellPath.toLowerCase().replace(/\//g, "\\");
|
|
32
|
+
return /\\windows\\(?:system32|sysnative)\\cmd\.exe$/.test(lower);
|
|
33
|
+
}
|
|
30
34
|
const isWindows = process.platform === "win32";
|
|
31
35
|
function commandExists(cmd) {
|
|
32
36
|
try {
|
|
@@ -130,38 +134,69 @@ function bunFallbackPaths() {
|
|
|
130
134
|
}
|
|
131
135
|
return home ? [`${home}/.bun/bin/bun`] : [];
|
|
132
136
|
}
|
|
137
|
+
/** Well-known Git-for-Windows bash.exe locations (MSYS bash that performs
|
|
138
|
+
* Windows→POSIX path conversion for native git — #826). */
|
|
139
|
+
const KNOWN_GIT_BASH_PATHS = [
|
|
140
|
+
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
141
|
+
"C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe",
|
|
142
|
+
];
|
|
133
143
|
/**
|
|
134
|
-
* On Windows, resolve the first non-WSL bash
|
|
135
|
-
*
|
|
136
|
-
*
|
|
144
|
+
* On Windows, resolve the first non-WSL bash that is actually available.
|
|
145
|
+
*
|
|
146
|
+
* Availability is gated by `where bash` (#796): bash must be discoverable on
|
|
147
|
+
* PATH for us to claim it. WSL bash (C:\Windows\System32\bash.exe) cannot
|
|
148
|
+
* handle Windows paths, so we skip it and prefer Git Bash / MSYS2 bash.
|
|
149
|
+
*
|
|
150
|
+
* Routing the gate through `where bash` — rather than probing the known Git
|
|
151
|
+
* Bash paths with existsSync first — is deliberate: when bash is genuinely
|
|
152
|
+
* unavailable, the caller must fall through to pwsh (PR intent). Probing the
|
|
153
|
+
* filesystem first re-detected a real Git Bash on the runner even though the
|
|
154
|
+
* scenario was "bash unavailable", so pwsh was never reached.
|
|
155
|
+
*
|
|
156
|
+
* #826 is preserved: when `where bash` surfaces a Git Bash candidate we
|
|
157
|
+
* canonicalize it to the absolute Git\usr\bin\bash.exe path (so native git
|
|
158
|
+
* keeps MSYS path conversion) by preferring a matching known path that exists.
|
|
137
159
|
*/
|
|
138
160
|
function resolveWindowsBash() {
|
|
139
|
-
|
|
140
|
-
// Git\usr\bin is not on PATH, which is common in MCP server environments
|
|
141
|
-
// that only inherit Git\cmd from the system PATH).
|
|
142
|
-
const knownPaths = [
|
|
143
|
-
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
144
|
-
"C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe",
|
|
145
|
-
];
|
|
146
|
-
for (const p of knownPaths) {
|
|
147
|
-
if (existsSync(p))
|
|
148
|
-
return p;
|
|
149
|
-
}
|
|
150
|
-
// Fallback: scan PATH via `where bash`, skipping WSL and WindowsApps entries.
|
|
161
|
+
let candidates;
|
|
151
162
|
try {
|
|
152
163
|
const result = execSync("where bash", { encoding: "utf-8", stdio: "pipe" });
|
|
153
|
-
|
|
154
|
-
for (const p of candidates) {
|
|
155
|
-
const lower = p.toLowerCase();
|
|
156
|
-
if (lower.includes("system32") || lower.includes("windowsapps"))
|
|
157
|
-
continue;
|
|
158
|
-
return p;
|
|
159
|
-
}
|
|
160
|
-
return null;
|
|
164
|
+
candidates = result.trim().split(/\r?\n/).map(p => p.trim()).filter(Boolean);
|
|
161
165
|
}
|
|
162
166
|
catch {
|
|
167
|
+
// bash not on PATH → genuinely unavailable. Fall through to pwsh/etc.
|
|
163
168
|
return null;
|
|
164
169
|
}
|
|
170
|
+
for (const p of candidates) {
|
|
171
|
+
const lower = p.toLowerCase();
|
|
172
|
+
if (lower.includes("system32") || lower.includes("windowsapps"))
|
|
173
|
+
continue;
|
|
174
|
+
// Prefer the canonical Git\usr\bin\bash.exe so native git retains MSYS
|
|
175
|
+
// path conversion. `where bash` on a Git-for-Windows install may surface
|
|
176
|
+
// the Git\cmd\bash shim or usr\bin path; upgrade to a known absolute path
|
|
177
|
+
// when one exists on disk.
|
|
178
|
+
for (const known of KNOWN_GIT_BASH_PATHS) {
|
|
179
|
+
if (existsSync(known))
|
|
180
|
+
return known;
|
|
181
|
+
}
|
|
182
|
+
return p;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function resolveWindowsShell(windowsBash = resolveWindowsBash()) {
|
|
187
|
+
// Prefer Git Bash (#826) so native git keeps its MSYS path conversion.
|
|
188
|
+
// The caller passes the already-resolved windowsBash to avoid probing the
|
|
189
|
+
// filesystem twice (it also feeds the cmd.exe shellOverride guard above).
|
|
190
|
+
// Fall back through POSIX sh, then PowerShell Core (pwsh) for proper UTF-8
|
|
191
|
+
// handling, then Windows PowerShell, then cmd.exe as the last resort.
|
|
192
|
+
return windowsBash
|
|
193
|
+
?? (commandExists("sh")
|
|
194
|
+
? "sh"
|
|
195
|
+
: commandExists("pwsh")
|
|
196
|
+
? "pwsh"
|
|
197
|
+
: commandExists("powershell")
|
|
198
|
+
? "powershell"
|
|
199
|
+
: "cmd.exe");
|
|
165
200
|
}
|
|
166
201
|
function getVersion(cmd, args = ["--version"]) {
|
|
167
202
|
try {
|
|
@@ -231,7 +266,17 @@ export function resolveJavascriptRuntime(bun, deps = {}) {
|
|
|
231
266
|
if (JS_RUNTIMES.has(base)) {
|
|
232
267
|
// Real JS runtime (node, bun, deno) — preserves #190 snap-Node fix
|
|
233
268
|
// because the snap wrapper's binary is literally named `node`.
|
|
234
|
-
|
|
269
|
+
//
|
|
270
|
+
// Issue #800 — liveness guard: on Homebrew, process.execPath points into
|
|
271
|
+
// the versioned Cellar (/opt/homebrew/Cellar/node/26.0.0/bin/node).
|
|
272
|
+
// `brew upgrade` + `brew cleanup` deletes the old Cellar, so the path
|
|
273
|
+
// dangles for the life of the already-running MCP server. If the path
|
|
274
|
+
// doesn't exist on disk, skip it and fall through to PATH node.
|
|
275
|
+
if (existsSync(execPath)) {
|
|
276
|
+
return execPath;
|
|
277
|
+
}
|
|
278
|
+
// Stale execPath (deleted Cellar, corrupted install, uninstall while
|
|
279
|
+
// process alive). Fall through to PATH resolution below.
|
|
235
280
|
}
|
|
236
281
|
// Host binary (opencode/kilo/etc.) — fall back to node on PATH.
|
|
237
282
|
if (cmdExists("node"))
|
|
@@ -252,10 +297,15 @@ export function detectRuntimes() {
|
|
|
252
297
|
// could redirect the executor to /usr/bin/python or any arbitrary binary.
|
|
253
298
|
const userShell = process.env.SHELL;
|
|
254
299
|
const isWin = process.platform === "win32";
|
|
300
|
+
const windowsBash = isWin ? resolveWindowsBash() : null;
|
|
255
301
|
const shellOverride = userShell &&
|
|
256
302
|
existsSync(userShell) &&
|
|
257
303
|
isAllowlistedShell(userShell) &&
|
|
258
|
-
!(isWin && isWindowsWslBash(userShell))
|
|
304
|
+
!(isWin && isWindowsWslBash(userShell)) &&
|
|
305
|
+
// Windows OpenSSH can inject the system cmd.exe as ambient SHELL. When
|
|
306
|
+
// Git Bash is installed, treating that as an explicit override breaks the
|
|
307
|
+
// POSIX shell executor path restored by #36/#384/#791.
|
|
308
|
+
!(isWin && windowsBash && isWindowsSystemCmd(userShell))
|
|
259
309
|
? userShell
|
|
260
310
|
: null;
|
|
261
311
|
return {
|
|
@@ -275,7 +325,7 @@ export function detectRuntimes() {
|
|
|
275
325
|
? "py"
|
|
276
326
|
: null,
|
|
277
327
|
shell: shellOverride ?? (isWin
|
|
278
|
-
? (
|
|
328
|
+
? resolveWindowsShell(windowsBash)
|
|
279
329
|
: commandExists("bash") ? "bash" : "sh"),
|
|
280
330
|
ruby: commandExists("ruby") ? "ruby" : null,
|
|
281
331
|
go: commandExists("go") ? "go" : null,
|
|
@@ -360,10 +410,40 @@ function bunVersionAtLeast1(versionOutput) {
|
|
|
360
410
|
* reason.
|
|
361
411
|
* - `ctx_upgrade` — separate path, must stay on Node for the same reason.
|
|
362
412
|
*/
|
|
413
|
+
/**
|
|
414
|
+
* Liveness-guarded Node path for the hook-runtime fallback (issue #841).
|
|
415
|
+
*
|
|
416
|
+
* `process.execPath` is pinned into every baked hook command because PATH
|
|
417
|
+
* resolution is unreliable for hooks (#190 snap-Node re-invokes the wrapper;
|
|
418
|
+
* #369 Windows Git Bash / MSYS can't resolve a bare `node`). But under a
|
|
419
|
+
* version manager (mise / asdf / nvm) execPath is a *version-pinned* absolute
|
|
420
|
+
* path — e.g. `~/.local/share/mise/installs/node/20.1.0/bin/node`. A routine
|
|
421
|
+
* `mise upgrade node` installs the next patch and DELETES the 20.1.0 dir, so
|
|
422
|
+
* the cached path dangles and every hook spawn fails with ENOENT — silently
|
|
423
|
+
* killing context-mode for that user.
|
|
424
|
+
*
|
|
425
|
+
* Same liveness-guard shape as the #800/#803 fix in
|
|
426
|
+
* {@link resolveJavascriptRuntime}: use the pinned execPath IFF it still
|
|
427
|
+
* exists on disk (preserving the #190/#369 reasons it was pinned), otherwise
|
|
428
|
+
* re-resolve a working `node` from PATH. The version manager's shim dir is on
|
|
429
|
+
* PATH and always points at the current patch, so bare `node` heals the host
|
|
430
|
+
* without a re-install. Falls back to the (stale) execPath only when no PATH
|
|
431
|
+
* node is reachable either — a strictly-better last resort than a dangling
|
|
432
|
+
* versioned path, and the doctor/upgrade flows surface the actionable error.
|
|
433
|
+
*/
|
|
434
|
+
function liveNodeRuntime() {
|
|
435
|
+
if (existsSync(process.execPath)) {
|
|
436
|
+
return { path: process.execPath, isBun: false };
|
|
437
|
+
}
|
|
438
|
+
if (commandExists("node")) {
|
|
439
|
+
return { path: "node", isBun: false };
|
|
440
|
+
}
|
|
441
|
+
return { path: process.execPath, isBun: false };
|
|
442
|
+
}
|
|
363
443
|
export function resolveHookRuntime() {
|
|
364
444
|
if (_hookRuntimeCache)
|
|
365
445
|
return _hookRuntimeCache;
|
|
366
|
-
const nodeFallback =
|
|
446
|
+
const nodeFallback = liveNodeRuntime();
|
|
367
447
|
try {
|
|
368
448
|
if (!bunExists()) {
|
|
369
449
|
_hookRuntimeCache = nodeFallback;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ctx_search flood-guard — per-agent-context progressive throttle.
|
|
3
|
+
*
|
|
4
|
+
* Background (#79 / #155 / #697): ctx_search carries a progressive throttle
|
|
5
|
+
* so a single actor cannot spam dozens of individual searches and flood the
|
|
6
|
+
* context window instead of batching via ctx_batch_execute. The original
|
|
7
|
+
* implementation kept ONE module-global counter on the MCP server process.
|
|
8
|
+
*
|
|
9
|
+
* Issue #769: a parallel multi-agent fan-out (Claude Code Task/Workflow)
|
|
10
|
+
* runs N subagents concurrently against the SAME per-session MCP server
|
|
11
|
+
* process. With a single global counter their independent calls are summed
|
|
12
|
+
* into one budget, so legitimate fan-out ("10 agents x 2 calls") trips the
|
|
13
|
+
* guard that was only ever meant to catch ONE actor spamming. The budget is
|
|
14
|
+
* tool-availability state that is logically per-agent-context, so the counter
|
|
15
|
+
* must be keyed per agent-context — NOT removed. Single-actor flood
|
|
16
|
+
* protection is preserved exactly; only the bucketing changes.
|
|
17
|
+
*
|
|
18
|
+
* This module is pure and transport-free so the policy is unit-testable
|
|
19
|
+
* without spinning up the MCP server. `src/server.ts` owns the singleton and
|
|
20
|
+
* supplies the per-call agent key (the session/agent id from
|
|
21
|
+
* currentAttribution()).
|
|
22
|
+
*/
|
|
23
|
+
export interface FloodGuardConfig {
|
|
24
|
+
/** Rolling window length in ms. After this elapses a key's counter resets. */
|
|
25
|
+
windowMs: number;
|
|
26
|
+
/** After this many calls in the window, results taper to 1 per query. */
|
|
27
|
+
softCapAfter: number;
|
|
28
|
+
/** After this many calls in the window, the call is hard-blocked. */
|
|
29
|
+
blockAfter: number;
|
|
30
|
+
}
|
|
31
|
+
export interface FloodDecision {
|
|
32
|
+
/** This key's call count within the current rolling window (1-based). */
|
|
33
|
+
count: number;
|
|
34
|
+
/** Window start timestamp (ms) for this key — used for the "in Ns" message. */
|
|
35
|
+
windowStart: number;
|
|
36
|
+
/** True once count exceeds blockAfter — caller must refuse the search. */
|
|
37
|
+
blocked: boolean;
|
|
38
|
+
/** True once count exceeds softCapAfter — caller trims to 1 result/query. */
|
|
39
|
+
softCapped: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A rolling-window call counter bucketed per agent-context key. Each key gets
|
|
43
|
+
* an independent window + counter, so concurrent subagents do not consume one
|
|
44
|
+
* another's budget while a single greedy actor is still throttled and blocked
|
|
45
|
+
* exactly as before.
|
|
46
|
+
*/
|
|
47
|
+
export declare class FloodGuard {
|
|
48
|
+
#private;
|
|
49
|
+
constructor(cfg: FloodGuardConfig, maxKeys?: number);
|
|
50
|
+
/**
|
|
51
|
+
* Record one ctx_search call for `key` at time `now` (ms) and return the
|
|
52
|
+
* throttle decision. Pure aside from the internal per-key counter state.
|
|
53
|
+
*/
|
|
54
|
+
record(key: string, now?: number): FloodDecision;
|
|
55
|
+
/** Test/diagnostics helper — number of distinct keys currently tracked. */
|
|
56
|
+
size(): number;
|
|
57
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ctx_search flood-guard — per-agent-context progressive throttle.
|
|
3
|
+
*
|
|
4
|
+
* Background (#79 / #155 / #697): ctx_search carries a progressive throttle
|
|
5
|
+
* so a single actor cannot spam dozens of individual searches and flood the
|
|
6
|
+
* context window instead of batching via ctx_batch_execute. The original
|
|
7
|
+
* implementation kept ONE module-global counter on the MCP server process.
|
|
8
|
+
*
|
|
9
|
+
* Issue #769: a parallel multi-agent fan-out (Claude Code Task/Workflow)
|
|
10
|
+
* runs N subagents concurrently against the SAME per-session MCP server
|
|
11
|
+
* process. With a single global counter their independent calls are summed
|
|
12
|
+
* into one budget, so legitimate fan-out ("10 agents x 2 calls") trips the
|
|
13
|
+
* guard that was only ever meant to catch ONE actor spamming. The budget is
|
|
14
|
+
* tool-availability state that is logically per-agent-context, so the counter
|
|
15
|
+
* must be keyed per agent-context — NOT removed. Single-actor flood
|
|
16
|
+
* protection is preserved exactly; only the bucketing changes.
|
|
17
|
+
*
|
|
18
|
+
* This module is pure and transport-free so the policy is unit-testable
|
|
19
|
+
* without spinning up the MCP server. `src/server.ts` owns the singleton and
|
|
20
|
+
* supplies the per-call agent key (the session/agent id from
|
|
21
|
+
* currentAttribution()).
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* A rolling-window call counter bucketed per agent-context key. Each key gets
|
|
25
|
+
* an independent window + counter, so concurrent subagents do not consume one
|
|
26
|
+
* another's budget while a single greedy actor is still throttled and blocked
|
|
27
|
+
* exactly as before.
|
|
28
|
+
*/
|
|
29
|
+
export class FloodGuard {
|
|
30
|
+
#cfg;
|
|
31
|
+
#buckets = new Map();
|
|
32
|
+
/**
|
|
33
|
+
* Hard ceiling on tracked keys — a defensive bound so a pathological host
|
|
34
|
+
* that mints unbounded distinct agent ids cannot grow the map without limit.
|
|
35
|
+
* When exceeded, the oldest-window bucket is evicted (its actor simply gets
|
|
36
|
+
* a fresh window on its next call — fail-open, never a false block).
|
|
37
|
+
*/
|
|
38
|
+
#maxKeys;
|
|
39
|
+
constructor(cfg, maxKeys = 4096) {
|
|
40
|
+
this.#cfg = cfg;
|
|
41
|
+
this.#maxKeys = Math.max(1, maxKeys);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Record one ctx_search call for `key` at time `now` (ms) and return the
|
|
45
|
+
* throttle decision. Pure aside from the internal per-key counter state.
|
|
46
|
+
*/
|
|
47
|
+
record(key, now = Date.now()) {
|
|
48
|
+
let bucket = this.#buckets.get(key);
|
|
49
|
+
if (!bucket || now - bucket.windowStart > this.#cfg.windowMs) {
|
|
50
|
+
bucket = { count: 0, windowStart: now };
|
|
51
|
+
this.#buckets.set(key, bucket);
|
|
52
|
+
this.#evictIfNeeded();
|
|
53
|
+
}
|
|
54
|
+
bucket.count++;
|
|
55
|
+
return {
|
|
56
|
+
count: bucket.count,
|
|
57
|
+
windowStart: bucket.windowStart,
|
|
58
|
+
blocked: bucket.count > this.#cfg.blockAfter,
|
|
59
|
+
softCapped: bucket.count > this.#cfg.softCapAfter,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Test/diagnostics helper — number of distinct keys currently tracked. */
|
|
63
|
+
size() {
|
|
64
|
+
return this.#buckets.size;
|
|
65
|
+
}
|
|
66
|
+
#evictIfNeeded() {
|
|
67
|
+
if (this.#buckets.size <= this.#maxKeys)
|
|
68
|
+
return;
|
|
69
|
+
let oldestKey;
|
|
70
|
+
let oldestStart = Infinity;
|
|
71
|
+
for (const [k, b] of this.#buckets) {
|
|
72
|
+
if (b.windowStart < oldestStart) {
|
|
73
|
+
oldestStart = b.windowStart;
|
|
74
|
+
oldestKey = k;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (oldestKey !== undefined)
|
|
78
|
+
this.#buckets.delete(oldestKey);
|
|
79
|
+
}
|
|
80
|
+
}
|
package/build/security.d.ts
CHANGED
|
@@ -41,14 +41,19 @@ export declare function fileGlobToRegex(glob: string, caseInsensitive?: boolean)
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function matchesAnyPattern(command: string, patterns: string[], caseInsensitive?: boolean): string | null;
|
|
43
43
|
/**
|
|
44
|
-
* Split a shell command on chain operators (&&, ||, ;,
|
|
45
|
-
* respecting single/double quotes and
|
|
44
|
+
* Split a shell command on chain operators (&&, ||, ;, |, \n, \r, &) while
|
|
45
|
+
* respecting single/double quotes, backticks, subshells, and escape backslashes.
|
|
46
46
|
*
|
|
47
47
|
* "echo hello && sudo rm -rf /" → ["echo hello", "sudo rm -rf /"]
|
|
48
48
|
*
|
|
49
49
|
* This prevents bypassing deny patterns by prepending innocent commands.
|
|
50
50
|
*/
|
|
51
51
|
export declare function splitChainedCommands(command: string): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Recursively extract all nested subshell commands from `$()` and `` `...` ``.
|
|
54
|
+
* Handles escaping and quote contexts to ensure correct command boundary detection.
|
|
55
|
+
*/
|
|
56
|
+
export declare function extractSubshellCommands(command: string): string[];
|
|
52
57
|
/**
|
|
53
58
|
* Read Bash permission policies from up to 3 settings files.
|
|
54
59
|
*
|
|
@@ -93,7 +98,7 @@ export declare function evaluateCommand(command: string, policies: SecurityPolic
|
|
|
93
98
|
* The server has no UI for "ask" prompts, so allow/ask patterns are
|
|
94
99
|
* irrelevant. Returns "deny" if any deny pattern matches, otherwise "allow".
|
|
95
100
|
*
|
|
96
|
-
* Also splits chained commands to prevent bypass.
|
|
101
|
+
* Also splits chained commands and nested subshells to prevent bypass.
|
|
97
102
|
*/
|
|
98
103
|
export declare function evaluateCommandDenyOnly(command: string, policies: SecurityPolicy[], caseInsensitive?: boolean): {
|
|
99
104
|
decision: "deny" | "allow";
|