context-mode 1.0.124 → 1.0.125
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/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +3 -3
- package/build/adapters/claude-code/hooks.d.ts +12 -13
- package/build/adapters/claude-code/hooks.js +11 -12
- package/build/adapters/claude-code/index.js +11 -5
- package/build/adapters/codex/hooks.d.ts +13 -14
- package/build/adapters/codex/hooks.js +13 -14
- package/build/adapters/codex/index.js +19 -8
- package/build/db-base.d.ts +19 -2
- package/build/db-base.js +49 -15
- package/build/executor.js +40 -3
- package/build/runtime.d.ts +2 -1
- package/build/runtime.js +10 -0
- package/build/server.js +4 -2
- package/cli.bundle.mjs +124 -121
- package/configs/codex/hooks.json +1 -1
- package/hooks/core/routing.mjs +8 -4
- package/hooks/hooks.json +1 -1
- package/hooks/session-db.bundle.mjs +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +79 -76
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.125"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "context-mode",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
16
|
-
"version": "1.0.
|
|
16
|
+
"version": "1.0.125",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Mert Koseoğlu"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.125",
|
|
4
4
|
"description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
|
@@ -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.125",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.125",
|
|
4
4
|
"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.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
package/README.md
CHANGED
|
@@ -570,7 +570,7 @@ Full documentation: [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md)
|
|
|
570
570
|
```json
|
|
571
571
|
{
|
|
572
572
|
"hooks": {
|
|
573
|
-
"PreToolUse": [{ "matcher": "local_shell|shell|shell_command|exec_command|
|
|
573
|
+
"PreToolUse": [{ "matcher": "local_shell|shell|shell_command|exec_command|Bash|Shell|apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__", "hooks": [{ "type": "command", "command": "context-mode hook codex pretooluse" }] }],
|
|
574
574
|
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex posttooluse" }] }],
|
|
575
575
|
"SessionStart": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex sessionstart" }] }],
|
|
576
576
|
"PreCompact": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex precompact" }] }],
|
|
@@ -964,7 +964,7 @@ npm install -g context-mode
|
|
|
964
964
|
| Tool | What it does | Context saved |
|
|
965
965
|
|---|---|---|
|
|
966
966
|
| `ctx_batch_execute` | Run multiple commands + search multiple queries in ONE call. Opt-in `concurrency: 1-8` for I/O-bound batches. | 986 KB → 62 KB |
|
|
967
|
-
| `ctx_execute` | Run code in
|
|
967
|
+
| `ctx_execute` | Run code in 12 languages. Only stdout enters context. | 56 KB → 299 B |
|
|
968
968
|
| `ctx_execute_file` | Process files in sandbox. Raw content never leaves. | 45 KB → 155 B |
|
|
969
969
|
| `ctx_index` | Chunk markdown into FTS5 with BM25 ranking. | 60 KB → 40 B |
|
|
970
970
|
| `ctx_search` | Query indexed content with multiple queries in one call. | On-demand retrieval |
|
|
@@ -978,7 +978,7 @@ npm install -g context-mode
|
|
|
978
978
|
|
|
979
979
|
Each `ctx_execute` call spawns an isolated subprocess with its own process boundary. Scripts can't access each other's memory or state. The subprocess runs your code, captures stdout, and only that stdout enters the conversation context. The raw data — log files, API responses, snapshots — never leaves the sandbox.
|
|
980
980
|
|
|
981
|
-
|
|
981
|
+
Twelve language runtimes are available: JavaScript, TypeScript, Python, Shell, Ruby, Go, Rust, PHP, Perl, R, Elixir, and C#. Bun is auto-detected for 3-5x faster JS/TS execution.
|
|
982
982
|
|
|
983
983
|
Authenticated CLIs work through credential passthrough — `gh`, `aws`, `gcloud`, `kubectl`, `docker` inherit environment variables and config paths without exposing them to the conversation.
|
|
984
984
|
|
|
@@ -25,22 +25,21 @@ export declare const HOOK_TYPES: {
|
|
|
25
25
|
};
|
|
26
26
|
export type HookType = (typeof HOOK_TYPES)[keyof typeof HOOK_TYPES];
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* External MCP catch-all matcher for Claude Code (#529, #547 hotfix).
|
|
29
29
|
*
|
|
30
|
-
* Claude Code's hook matcher engine
|
|
31
|
-
*
|
|
32
|
-
* server
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* context-mode's own ctx_* tools, which already have dedicated entries above.
|
|
30
|
+
* Claude Code's hook matcher engine treats this entry as a substring match
|
|
31
|
+
* (it also accepts regex, but `mcp__` alone is enough — every MCP tool
|
|
32
|
+
* surfaces as `mcp__<server>__<tool>`). v1.0.124 used a negative lookahead
|
|
33
|
+
* `mcp__(?!plugin_context-mode_)` to skip context-mode's own MCP tools,
|
|
34
|
+
* but this same hooks.json is bundled to Codex CLI which uses Rust's
|
|
35
|
+
* `regex` crate (no look-around support) — Codex rejected the matcher at
|
|
36
|
+
* boot, breaking every Codex user (#547). Drop the lookaround on both
|
|
37
|
+
* sides; the hook BODY (`isExternalMcpTool()` in hooks/core/routing.mjs)
|
|
38
|
+
* already filters context-mode's own tools, so semantics are preserved.
|
|
40
39
|
*/
|
|
41
|
-
export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__
|
|
40
|
+
export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__";
|
|
42
41
|
/** Tools that context-mode's PreToolUse hook intercepts. */
|
|
43
|
-
export declare const PRE_TOOL_USE_MATCHERS: readonly ["Bash", "WebFetch", "Read", "Grep", "Agent", "mcp__plugin_context-mode_context-mode__ctx_execute", "mcp__plugin_context-mode_context-mode__ctx_execute_file", "mcp__plugin_context-mode_context-mode__ctx_batch_execute", "mcp__
|
|
42
|
+
export declare const PRE_TOOL_USE_MATCHERS: readonly ["Bash", "WebFetch", "Read", "Grep", "Agent", "mcp__plugin_context-mode_context-mode__ctx_execute", "mcp__plugin_context-mode_context-mode__ctx_execute_file", "mcp__plugin_context-mode_context-mode__ctx_batch_execute", "mcp__"];
|
|
44
43
|
/**
|
|
45
44
|
* Combined matcher pattern for settings.json (pipe-separated).
|
|
46
45
|
* Used by the upgrade command when writing a single consolidated entry.
|
|
@@ -31,20 +31,19 @@ export const HOOK_TYPES = {
|
|
|
31
31
|
// PreToolUse matchers
|
|
32
32
|
// ─────────────────────────────────────────────────────────
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* External MCP catch-all matcher for Claude Code (#529, #547 hotfix).
|
|
35
35
|
*
|
|
36
|
-
* Claude Code's hook matcher engine
|
|
37
|
-
*
|
|
38
|
-
* server
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* context-mode's own ctx_* tools, which already have dedicated entries above.
|
|
36
|
+
* Claude Code's hook matcher engine treats this entry as a substring match
|
|
37
|
+
* (it also accepts regex, but `mcp__` alone is enough — every MCP tool
|
|
38
|
+
* surfaces as `mcp__<server>__<tool>`). v1.0.124 used a negative lookahead
|
|
39
|
+
* `mcp__(?!plugin_context-mode_)` to skip context-mode's own MCP tools,
|
|
40
|
+
* but this same hooks.json is bundled to Codex CLI which uses Rust's
|
|
41
|
+
* `regex` crate (no look-around support) — Codex rejected the matcher at
|
|
42
|
+
* boot, breaking every Codex user (#547). Drop the lookaround on both
|
|
43
|
+
* sides; the hook BODY (`isExternalMcpTool()` in hooks/core/routing.mjs)
|
|
44
|
+
* already filters context-mode's own tools, so semantics are preserved.
|
|
46
45
|
*/
|
|
47
|
-
export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__
|
|
46
|
+
export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__";
|
|
48
47
|
/** Tools that context-mode's PreToolUse hook intercepts. */
|
|
49
48
|
export const PRE_TOOL_USE_MATCHERS = [
|
|
50
49
|
"Bash",
|
|
@@ -67,7 +67,13 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
67
67
|
return join(this.getConfigDir(), "settings.json");
|
|
68
68
|
}
|
|
69
69
|
generateHookConfig(pluginRoot) {
|
|
70
|
-
|
|
70
|
+
// Paths must be double-quoted so that hosts (or our own diagnostic
|
|
71
|
+
// regex) parse them correctly when pluginRoot contains spaces — common
|
|
72
|
+
// on Windows where the user folder is e.g. "C:\Users\First Last\...".
|
|
73
|
+
// Without quotes, extractHookScriptPath's `\S+\.mjs` fallback grabs
|
|
74
|
+
// only the tail after the last space, producing a doubled-path FAIL
|
|
75
|
+
// in `ctx doctor`. Matches the quoting style used in hooks/hooks.json.
|
|
76
|
+
const preToolUseCommand = `node "${pluginRoot}/hooks/pretooluse.mjs"`;
|
|
71
77
|
const preToolUseMatchers = [...PRE_TOOL_USE_MATCHERS];
|
|
72
78
|
return {
|
|
73
79
|
PreToolUse: preToolUseMatchers.map((matcher) => ({
|
|
@@ -80,7 +86,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
80
86
|
hooks: [
|
|
81
87
|
{
|
|
82
88
|
type: "command",
|
|
83
|
-
command: `node ${pluginRoot}/hooks/posttooluse.mjs`,
|
|
89
|
+
command: `node "${pluginRoot}/hooks/posttooluse.mjs"`,
|
|
84
90
|
},
|
|
85
91
|
],
|
|
86
92
|
},
|
|
@@ -91,7 +97,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
91
97
|
hooks: [
|
|
92
98
|
{
|
|
93
99
|
type: "command",
|
|
94
|
-
command: `node ${pluginRoot}/hooks/precompact.mjs`,
|
|
100
|
+
command: `node "${pluginRoot}/hooks/precompact.mjs"`,
|
|
95
101
|
},
|
|
96
102
|
],
|
|
97
103
|
},
|
|
@@ -102,7 +108,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
102
108
|
hooks: [
|
|
103
109
|
{
|
|
104
110
|
type: "command",
|
|
105
|
-
command: `node ${pluginRoot}/hooks/userpromptsubmit.mjs`,
|
|
111
|
+
command: `node "${pluginRoot}/hooks/userpromptsubmit.mjs"`,
|
|
106
112
|
},
|
|
107
113
|
],
|
|
108
114
|
},
|
|
@@ -113,7 +119,7 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
113
119
|
hooks: [
|
|
114
120
|
{
|
|
115
121
|
type: "command",
|
|
116
|
-
command: `node ${pluginRoot}/hooks/sessionstart.mjs`,
|
|
122
|
+
command: `node "${pluginRoot}/hooks/sessionstart.mjs"`,
|
|
117
123
|
},
|
|
118
124
|
],
|
|
119
125
|
},
|
|
@@ -27,24 +27,23 @@ export declare const HOOK_TYPES: {
|
|
|
27
27
|
readonly STOP: "Stop";
|
|
28
28
|
};
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* External MCP catch-all matcher for Codex CLI (#529, #547 hotfix).
|
|
31
31
|
*
|
|
32
32
|
* Codex CLI's hook `tool_name` payload uses `mcp__<server>__<tool>` for any
|
|
33
|
-
* MCP-namespaced tool
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* and flood the model's context — PostToolUse runs too late to keep raw data
|
|
39
|
-
* out.
|
|
33
|
+
* MCP-namespaced tool. Originally this constant used a negative lookahead
|
|
34
|
+
* `mcp__(?!.*context-mode)` to exclude context-mode's own MCP tools at the
|
|
35
|
+
* matcher layer. v1.0.124 shipped that pattern and Codex (Rust `regex` crate)
|
|
36
|
+
* rejected the matcher at boot with "look-around not supported", breaking
|
|
37
|
+
* every Codex user (#547).
|
|
40
38
|
*
|
|
41
|
-
* The
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
39
|
+
* Fix: drop the lookaround. The matcher is now a charset-clean literal
|
|
40
|
+
* (`[A-Za-z0-9_|]` only), satisfying Codex's `is_exact_matcher`
|
|
41
|
+
* (refs/platforms/codex/codex-rs/hooks/src/events/common.rs:152) which
|
|
42
|
+
* short-circuits the regex engine entirely. context-mode's own MCP tools are
|
|
43
|
+
* already filtered in the hook BODY by `isExternalMcpTool()` in
|
|
44
|
+
* hooks/core/routing.mjs — semantics preserved.
|
|
46
45
|
*/
|
|
47
|
-
export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__
|
|
46
|
+
export declare const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__";
|
|
48
47
|
/**
|
|
49
48
|
* Path to the routing instructions file for Codex CLI.
|
|
50
49
|
* Used as fallback routing awareness alongside hook-based enforcement.
|
|
@@ -33,24 +33,23 @@ export const HOOK_TYPES = {
|
|
|
33
33
|
// External MCP routing matcher (#529)
|
|
34
34
|
// ─────────────────────────────────────────────────────────
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* External MCP catch-all matcher for Codex CLI (#529, #547 hotfix).
|
|
37
37
|
*
|
|
38
38
|
* Codex CLI's hook `tool_name` payload uses `mcp__<server>__<tool>` for any
|
|
39
|
-
* MCP-namespaced tool
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* and flood the model's context — PostToolUse runs too late to keep raw data
|
|
45
|
-
* out.
|
|
39
|
+
* MCP-namespaced tool. Originally this constant used a negative lookahead
|
|
40
|
+
* `mcp__(?!.*context-mode)` to exclude context-mode's own MCP tools at the
|
|
41
|
+
* matcher layer. v1.0.124 shipped that pattern and Codex (Rust `regex` crate)
|
|
42
|
+
* rejected the matcher at boot with "look-around not supported", breaking
|
|
43
|
+
* every Codex user (#547).
|
|
46
44
|
*
|
|
47
|
-
* The
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
45
|
+
* Fix: drop the lookaround. The matcher is now a charset-clean literal
|
|
46
|
+
* (`[A-Za-z0-9_|]` only), satisfying Codex's `is_exact_matcher`
|
|
47
|
+
* (refs/platforms/codex/codex-rs/hooks/src/events/common.rs:152) which
|
|
48
|
+
* short-circuits the regex engine entirely. context-mode's own MCP tools are
|
|
49
|
+
* already filtered in the hook BODY by `isExternalMcpTool()` in
|
|
50
|
+
* hooks/core/routing.mjs — semantics preserved.
|
|
52
51
|
*/
|
|
53
|
-
export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__
|
|
52
|
+
export const EXTERNAL_MCP_MATCHER_PATTERN = "mcp__";
|
|
54
53
|
// ─────────────────────────────────────────────────────────
|
|
55
54
|
// Routing instructions
|
|
56
55
|
// ─────────────────────────────────────────────────────────
|
|
@@ -18,14 +18,25 @@ import { resolve, dirname, join } from "node:path";
|
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { BaseAdapter } from "../base.js";
|
|
20
20
|
import { resolveCodexConfigDir } from "./paths.js";
|
|
21
|
-
// PreToolUse matcher: canonical Codex tool names + context-mode
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
21
|
+
// PreToolUse matcher: canonical Codex tool names + context-mode bare MCP tool
|
|
22
|
+
// names + external MCP catch-all literal (#529, #547 hotfix).
|
|
23
|
+
//
|
|
24
|
+
// Codex CLI's Rust `regex` crate does NOT support look-around, and
|
|
25
|
+
// `is_exact_matcher` (refs/platforms/codex/codex-rs/hooks/src/events/common.rs:152)
|
|
26
|
+
// short-circuits the regex engine entirely when the matcher contains only
|
|
27
|
+
// [A-Za-z0-9_|]. v1.0.124 shipped a matcher with `(?!.*context-mode)` AND
|
|
28
|
+
// `mcp__.*__ctx_*` regex syntax — Codex rejected the file at boot with
|
|
29
|
+
// "look-around not supported" → all v1.0.124 Codex users broken (#547).
|
|
30
|
+
//
|
|
31
|
+
// Fix: keep only literal tool names (charset-clean). The hook BODY already
|
|
32
|
+
// filters context-mode's own MCP tools via `isExternalMcpTool()` in
|
|
33
|
+
// hooks/core/routing.mjs, so dropping `mcp__.*__ctx_*` and the lookaround
|
|
34
|
+
// preserves end-to-end semantics. The literal `mcp__` final segment is a
|
|
35
|
+
// no-op under exact-matcher mode but kept for parity with hooks/hooks.json.
|
|
36
|
+
//
|
|
37
|
+
// Keep this as a single string literal — `codex.test.ts` drift-guard parses
|
|
38
|
+
// the source with a `"([^"]+)"` regex.
|
|
39
|
+
const PRE_TOOL_USE_MATCHER_PATTERN = "local_shell|shell|shell_command|exec_command|Bash|Shell|apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__";
|
|
29
40
|
const CODEX_HOOK_COMMANDS = {
|
|
30
41
|
PreToolUse: "context-mode hook codex pretooluse",
|
|
31
42
|
PostToolUse: "context-mode hook codex posttooluse",
|
package/build/db-base.d.ts
CHANGED
|
@@ -61,11 +61,28 @@ export declare class NodeSQLiteAdapter {
|
|
|
61
61
|
* bundled SQLite always ships with FTS5.
|
|
62
62
|
*/
|
|
63
63
|
export declare function nodeSqliteHasFts5(DatabaseSync: any): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Returns true when the current runtime ships a built-in SQLite binding:
|
|
66
|
+
* - Bun has `bun:sqlite` always
|
|
67
|
+
* - Node has `node:sqlite` since 22.5 (no flag since 22.13)
|
|
68
|
+
*
|
|
69
|
+
* Mirrors the helper in hooks/ensure-deps.mjs:61. Exported so the platform
|
|
70
|
+
* gate in loadDatabase() can be unit-tested without spawning a child
|
|
71
|
+
* process. `versionsOverride` and `bunOverride` are injection points for
|
|
72
|
+
* tests — production callers pass nothing.
|
|
73
|
+
*
|
|
74
|
+
* Widening the gate from `process.platform === "linux"` to this helper is
|
|
75
|
+
* required for Node 26 on macOS arm64 (#551): Node 26 removed
|
|
76
|
+
* `info.This()` from V8 PropertyCallbackInfo, breaking better-sqlite3
|
|
77
|
+
* 12.9.0's native compile. Using node:sqlite sidesteps the native addon
|
|
78
|
+
* entirely on every platform that has it.
|
|
79
|
+
*/
|
|
80
|
+
export declare function hasModernSqlite(versionsOverride?: NodeJS.ProcessVersions, bunOverride?: unknown): boolean;
|
|
64
81
|
/**
|
|
65
82
|
* Lazy-load the SQLite driver for the current runtime.
|
|
66
83
|
* Bun → bun:sqlite via BunSQLiteAdapter (issue #45).
|
|
67
|
-
*
|
|
68
|
-
* Other Node (or
|
|
84
|
+
* Modern Node (>= 22.5) → node:sqlite via NodeSQLiteAdapter when it ships FTS5 (#228, #461, #551).
|
|
85
|
+
* Other Node (or modern Node without FTS5) → better-sqlite3 (native addon).
|
|
69
86
|
*/
|
|
70
87
|
export declare function loadDatabase(): typeof DatabaseConstructor;
|
|
71
88
|
/**
|
package/build/db-base.js
CHANGED
|
@@ -184,11 +184,39 @@ export function nodeSqliteHasFts5(DatabaseSync) {
|
|
|
184
184
|
catch { /* probe never opened or already closed */ }
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Returns true when the current runtime ships a built-in SQLite binding:
|
|
189
|
+
* - Bun has `bun:sqlite` always
|
|
190
|
+
* - Node has `node:sqlite` since 22.5 (no flag since 22.13)
|
|
191
|
+
*
|
|
192
|
+
* Mirrors the helper in hooks/ensure-deps.mjs:61. Exported so the platform
|
|
193
|
+
* gate in loadDatabase() can be unit-tested without spawning a child
|
|
194
|
+
* process. `versionsOverride` and `bunOverride` are injection points for
|
|
195
|
+
* tests — production callers pass nothing.
|
|
196
|
+
*
|
|
197
|
+
* Widening the gate from `process.platform === "linux"` to this helper is
|
|
198
|
+
* required for Node 26 on macOS arm64 (#551): Node 26 removed
|
|
199
|
+
* `info.This()` from V8 PropertyCallbackInfo, breaking better-sqlite3
|
|
200
|
+
* 12.9.0's native compile. Using node:sqlite sidesteps the native addon
|
|
201
|
+
* entirely on every platform that has it.
|
|
202
|
+
*/
|
|
203
|
+
export function hasModernSqlite(versionsOverride, bunOverride) {
|
|
204
|
+
const bun = bunOverride !== undefined ? bunOverride : globalThis.Bun;
|
|
205
|
+
if (typeof bun !== "undefined" && bun !== null)
|
|
206
|
+
return true;
|
|
207
|
+
const versions = versionsOverride ?? process.versions;
|
|
208
|
+
const [majorStr, minorStr] = (versions.node ?? "0.0.0").split(".");
|
|
209
|
+
const major = Number(majorStr);
|
|
210
|
+
const minor = Number(minorStr);
|
|
211
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor))
|
|
212
|
+
return false;
|
|
213
|
+
return major > 22 || (major === 22 && minor >= 5);
|
|
214
|
+
}
|
|
187
215
|
/**
|
|
188
216
|
* Lazy-load the SQLite driver for the current runtime.
|
|
189
217
|
* Bun → bun:sqlite via BunSQLiteAdapter (issue #45).
|
|
190
|
-
*
|
|
191
|
-
* Other Node (or
|
|
218
|
+
* Modern Node (>= 22.5) → node:sqlite via NodeSQLiteAdapter when it ships FTS5 (#228, #461, #551).
|
|
219
|
+
* Other Node (or modern Node without FTS5) → better-sqlite3 (native addon).
|
|
192
220
|
*/
|
|
193
221
|
export function loadDatabase() {
|
|
194
222
|
if (!_Database) {
|
|
@@ -211,13 +239,19 @@ export function loadDatabase() {
|
|
|
211
239
|
return adapter;
|
|
212
240
|
};
|
|
213
241
|
}
|
|
214
|
-
else if (
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
242
|
+
else if (hasModernSqlite()) {
|
|
243
|
+
// Any Node >= 22.5 — try node:sqlite to avoid the native addon path
|
|
244
|
+
// entirely. Historically this was Linux-only (avoiding the Linux
|
|
245
|
+
// SIGSEGV per nodejs/node#62515, #228), but Node 26 also broke
|
|
246
|
+
// better-sqlite3's native compile on macOS arm64 by removing
|
|
247
|
+
// V8 `info.This()` (#551). The built-in `node:sqlite` ships its
|
|
248
|
+
// own SQLite, so it sidesteps both issues at once.
|
|
249
|
+
//
|
|
250
|
+
// Probe FTS5 support before committing — some Node builds ship
|
|
251
|
+
// node:sqlite without FTS5, which would silently break ctx_search
|
|
252
|
+
// (#461). The probe runs at most once per process (cached via
|
|
253
|
+
// _Database below), so the cost of an in-memory DatabaseSync is
|
|
254
|
+
// negligible.
|
|
221
255
|
let DatabaseSync = null;
|
|
222
256
|
try {
|
|
223
257
|
// Array.join() prevents esbuild from resolving the specifier at bundle time
|
|
@@ -236,16 +270,16 @@ export function loadDatabase() {
|
|
|
236
270
|
};
|
|
237
271
|
}
|
|
238
272
|
else {
|
|
239
|
-
// node:sqlite missing or built without FTS5 — fall through to
|
|
240
|
-
// Trade-off:
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
273
|
+
// node:sqlite missing or built without FTS5 — fall through to
|
|
274
|
+
// better-sqlite3. Trade-off: on Node 26 + macOS this may now hit
|
|
275
|
+
// the V8 ABI break (#551). A visible crash on the rare
|
|
276
|
+
// unstable build is preferable to silent "no such module: fts5"
|
|
277
|
+
// on every ctx_search call.
|
|
244
278
|
_Database = require("better-sqlite3");
|
|
245
279
|
}
|
|
246
280
|
}
|
|
247
281
|
else {
|
|
248
|
-
//
|
|
282
|
+
// Old Node (< 22.5) without bun:sqlite — fall back to better-sqlite3.
|
|
249
283
|
_Database = require("better-sqlite3");
|
|
250
284
|
}
|
|
251
285
|
}
|
package/build/executor.js
CHANGED
|
@@ -23,6 +23,7 @@ const SCRIPT_EXT = {
|
|
|
23
23
|
perl: "pl",
|
|
24
24
|
r: "R",
|
|
25
25
|
elixir: "exs",
|
|
26
|
+
csharp: "csx",
|
|
26
27
|
};
|
|
27
28
|
/** Pure helper — exported for unit testing. Returns "script" or "script.<ext>". */
|
|
28
29
|
export function buildScriptFilename(language, platform, shellPath) {
|
|
@@ -223,7 +224,7 @@ export class PolyglotExecutor {
|
|
|
223
224
|
// .exe paths now (#506), but if it falls back to the bare "bun" string
|
|
224
225
|
// on Windows that resolution typically goes through a `bun.cmd` shim
|
|
225
226
|
// (npm i -g bun) which CreateProcess can't execute without cmd.exe.
|
|
226
|
-
const needsShell = isWin && ["tsx", "ts-node", "elixir", "bun"].includes(cmd[0]);
|
|
227
|
+
const needsShell = isWin && ["tsx", "ts-node", "elixir", "bun", "dotnet-script"].includes(cmd[0]);
|
|
227
228
|
// On Windows with Git Bash, pass the script as `bash -c "source /posix/path"`
|
|
228
229
|
// rather than `bash /path/to/script.sh`. This avoids MSYS2 path mangling
|
|
229
230
|
// while still allowing MSYS_NO_PATHCONV to protect non-ASCII paths in commands.
|
|
@@ -412,6 +413,30 @@ export class PolyglotExecutor {
|
|
|
412
413
|
"R_PROFILE", // site-wide R profile
|
|
413
414
|
"R_PROFILE_USER", // user R profile
|
|
414
415
|
"R_HOME", // R installation override
|
|
416
|
+
// .NET / C# — runtime/startup hooks, additional deps
|
|
417
|
+
"DOTNET_STARTUP_HOOKS", // injects managed assemblies on startup
|
|
418
|
+
"DOTNET_ADDITIONAL_DEPS", // additional .deps.json injection
|
|
419
|
+
"DOTNET_SHARED_STORE", // shared assembly probe path injection
|
|
420
|
+
"DOTNET_ROOT", // arbitrary .NET runtime override
|
|
421
|
+
"DOTNET_ROOT(x86)", // 32-bit override
|
|
422
|
+
"DOTNET_HOST_PATH", // host binary substitution
|
|
423
|
+
// .NET / C# — profiler attach (loads arbitrary DLL into dotnet host)
|
|
424
|
+
// and IPC-based debugger/IL injection. PR #546 follow-up.
|
|
425
|
+
// learn.microsoft.com/en-us/dotnet/core/runtime-config/debugging-profiling
|
|
426
|
+
"CORECLR_PROFILER", // CLSID of profiler to attach
|
|
427
|
+
"CORECLR_PROFILER_PATH", // path to profiler DLL
|
|
428
|
+
"CORECLR_PROFILER_PATH_32", // 32-bit specific profiler DLL
|
|
429
|
+
"CORECLR_PROFILER_PATH_64", // 64-bit specific profiler DLL
|
|
430
|
+
"CORECLR_PROFILER_PATH_ARM32", // ARM32 specific profiler DLL
|
|
431
|
+
"CORECLR_PROFILER_PATH_ARM64", // ARM64 specific profiler DLL
|
|
432
|
+
"CORECLR_ENABLE_PROFILING", // gates profiler load
|
|
433
|
+
"DOTNET_PROFILER_PATH", // cross-platform alias
|
|
434
|
+
"DOTNET_PROFILER_PATH_32",
|
|
435
|
+
"DOTNET_PROFILER_PATH_64",
|
|
436
|
+
"DOTNET_PROFILER_PATH_ARM32",
|
|
437
|
+
"DOTNET_PROFILER_PATH_ARM64",
|
|
438
|
+
"DOTNET_DiagnosticPorts", // peer attach via diagnostic IPC
|
|
439
|
+
"DOTNET_BUNDLE_EXTRACT_BASE_DIR", // single-file extraction hijack
|
|
415
440
|
// Dynamic linker — shared library injection
|
|
416
441
|
"LD_PRELOAD", // loads .so before all others (Linux)
|
|
417
442
|
"DYLD_INSERT_LIBRARIES", // macOS equivalent of LD_PRELOAD
|
|
@@ -431,10 +456,17 @@ export class PolyglotExecutor {
|
|
|
431
456
|
"GIT_SSH_COMMAND", // arbitrary ssh command
|
|
432
457
|
"GIT_ASKPASS", // arbitrary credential command
|
|
433
458
|
]);
|
|
434
|
-
// Start with parent env, then strip dangerous vars and apply overrides
|
|
459
|
+
// Start with parent env, then strip dangerous vars and apply overrides.
|
|
460
|
+
// The `COMPlus_` prefix sweep covers every COMPlus_* synonym of the
|
|
461
|
+
// DOTNET_* runtime knobs (.NET back-compat alias — case-insensitive).
|
|
462
|
+
// PR #546 follow-up: closes the alias bypass for the explicit denylist
|
|
463
|
+
// entries above.
|
|
435
464
|
const env = {};
|
|
436
465
|
for (const [key, val] of Object.entries(process.env)) {
|
|
437
|
-
if (val !== undefined &&
|
|
466
|
+
if (val !== undefined &&
|
|
467
|
+
!DENIED.has(key) &&
|
|
468
|
+
!key.startsWith("BASH_FUNC_") &&
|
|
469
|
+
!/^COMPlus_/i.test(key)) {
|
|
438
470
|
env[key] = val;
|
|
439
471
|
}
|
|
440
472
|
}
|
|
@@ -508,6 +540,11 @@ export class PolyglotExecutor {
|
|
|
508
540
|
return `FILE_CONTENT_PATH <- ${escaped}\nfile_path <- FILE_CONTENT_PATH\nFILE_CONTENT <- readLines(FILE_CONTENT_PATH, warn=FALSE, encoding="UTF-8")\nFILE_CONTENT <- paste(FILE_CONTENT, collapse="\\n")\n${code}`;
|
|
509
541
|
case "elixir":
|
|
510
542
|
return `file_content_path = ${escaped}\nfile_path = file_content_path\nfile_content = File.read!(file_content_path)\n${code}`;
|
|
543
|
+
case "csharp":
|
|
544
|
+
// .csx forbids `using` directives after any other top-level statement
|
|
545
|
+
// (CS1529). User code inside executeFile must use fully-qualified type
|
|
546
|
+
// names (e.g. `System.Text.Json.JsonDocument`) instead of `using`.
|
|
547
|
+
return `var FILE_CONTENT_PATH = ${escaped};\nvar file_path = FILE_CONTENT_PATH;\nvar FILE_CONTENT = System.IO.File.ReadAllText(FILE_CONTENT_PATH);\n${code}`;
|
|
511
548
|
}
|
|
512
549
|
}
|
|
513
550
|
}
|
package/build/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function isAllowlistedShell(shellPath: string): boolean;
|
|
2
|
-
export type Language = "javascript" | "typescript" | "python" | "shell" | "ruby" | "go" | "rust" | "php" | "perl" | "r" | "elixir";
|
|
2
|
+
export type Language = "javascript" | "typescript" | "python" | "shell" | "ruby" | "go" | "rust" | "php" | "perl" | "r" | "elixir" | "csharp";
|
|
3
3
|
export interface RuntimeInfo {
|
|
4
4
|
command: string;
|
|
5
5
|
available: boolean;
|
|
@@ -18,6 +18,7 @@ export interface RuntimeMap {
|
|
|
18
18
|
perl: string | null;
|
|
19
19
|
r: string | null;
|
|
20
20
|
elixir: string | null;
|
|
21
|
+
csharp: string | null;
|
|
21
22
|
}
|
|
22
23
|
export declare function detectRuntimes(): RuntimeMap;
|
|
23
24
|
export declare function hasBunRuntime(): boolean;
|
package/build/runtime.js
CHANGED
|
@@ -232,6 +232,7 @@ export function detectRuntimes() {
|
|
|
232
232
|
? "r"
|
|
233
233
|
: null,
|
|
234
234
|
elixir: commandExists("elixir") ? "elixir" : null,
|
|
235
|
+
csharp: commandExists("dotnet-script") ? "dotnet-script" : null,
|
|
235
236
|
};
|
|
236
237
|
}
|
|
237
238
|
export function hasBunRuntime() {
|
|
@@ -269,6 +270,8 @@ export function getRuntimeSummary(runtimes) {
|
|
|
269
270
|
lines.push(` R: ${runtimes.r} (${getVersion(runtimes.r)})`);
|
|
270
271
|
if (runtimes.elixir)
|
|
271
272
|
lines.push(` Elixir: ${runtimes.elixir} (${getVersion(runtimes.elixir)})`);
|
|
273
|
+
if (runtimes.csharp)
|
|
274
|
+
lines.push(` C#: ${runtimes.csharp} (${getVersion(runtimes.csharp)})`);
|
|
272
275
|
if (!bunPreferred) {
|
|
273
276
|
lines.push("");
|
|
274
277
|
lines.push(" Tip: Install Bun for 3-5x faster JS/TS execution → https://bun.sh");
|
|
@@ -295,6 +298,8 @@ export function getAvailableLanguages(runtimes) {
|
|
|
295
298
|
langs.push("r");
|
|
296
299
|
if (runtimes.elixir)
|
|
297
300
|
langs.push("elixir");
|
|
301
|
+
if (runtimes.csharp)
|
|
302
|
+
langs.push("csharp");
|
|
298
303
|
return langs;
|
|
299
304
|
}
|
|
300
305
|
export function buildCommand(runtimes, language, filePath) {
|
|
@@ -376,5 +381,10 @@ export function buildCommand(runtimes, language, filePath) {
|
|
|
376
381
|
throw new Error("Elixir not available. Install elixir.");
|
|
377
382
|
}
|
|
378
383
|
return ["elixir", filePath];
|
|
384
|
+
case "csharp":
|
|
385
|
+
if (!runtimes.csharp) {
|
|
386
|
+
throw new Error("C# not available. Install dotnet-script via `dotnet tool install -g dotnet-script`.");
|
|
387
|
+
}
|
|
388
|
+
return [runtimes.csharp, filePath];
|
|
379
389
|
}
|
|
380
390
|
}
|
package/build/server.js
CHANGED
|
@@ -929,11 +929,12 @@ server.registerTool("ctx_execute", {
|
|
|
929
929
|
"perl",
|
|
930
930
|
"r",
|
|
931
931
|
"elixir",
|
|
932
|
+
"csharp",
|
|
932
933
|
])
|
|
933
934
|
.describe("Runtime language"),
|
|
934
935
|
code: z
|
|
935
936
|
.string()
|
|
936
|
-
.describe("Source code to execute. Use console.log (JS/TS), print (Python/Ruby/Perl/R), echo (Shell), echo (PHP), fmt.Println (Go),
|
|
937
|
+
.describe("Source code to execute. Use console.log (JS/TS), print (Python/Ruby/Perl/R), echo (Shell), echo (PHP), fmt.Println (Go), IO.puts (Elixir), or Console.WriteLine (C#) to output a summary to context."),
|
|
937
938
|
timeout: z
|
|
938
939
|
.coerce.number()
|
|
939
940
|
.optional()
|
|
@@ -1224,11 +1225,12 @@ server.registerTool("ctx_execute_file", {
|
|
|
1224
1225
|
"perl",
|
|
1225
1226
|
"r",
|
|
1226
1227
|
"elixir",
|
|
1228
|
+
"csharp",
|
|
1227
1229
|
])
|
|
1228
1230
|
.describe("Runtime language"),
|
|
1229
1231
|
code: z
|
|
1230
1232
|
.string()
|
|
1231
|
-
.describe("Code to process FILE_CONTENT (file_content in Elixir). Print summary via console.log/print/echo/IO.puts."),
|
|
1233
|
+
.describe("Code to process FILE_CONTENT (file_content in Elixir). Print summary via console.log/print/echo/IO.puts/Console.WriteLine."),
|
|
1232
1234
|
timeout: z
|
|
1233
1235
|
.coerce.number()
|
|
1234
1236
|
.optional()
|