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,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin cache self-heal — fixes broken CLAUDE_PLUGIN_ROOT references.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code's plugin auto-update can leave installed_plugins.json pointing
|
|
5
|
+
* to a non-existent directory (anthropics/claude-code#46915). This module
|
|
6
|
+
* detects and repairs the mismatch by creating symlinks.
|
|
7
|
+
*
|
|
8
|
+
* 4-layer defense:
|
|
9
|
+
* 1. start.mjs startup — reverse heal (registry → symlink to us)
|
|
10
|
+
* 2. server.ts first tool call — mid-session heal
|
|
11
|
+
* 3. postinstall.mjs — backward symlink on new install
|
|
12
|
+
* 4. global hook auto-deploy — survives total plugin cache breakage
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readFileSync, symlinkSync, mkdirSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { resolve, dirname } from "node:path";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
/**
|
|
18
|
+
* Core heal: if installed_plugins.json points to a non-existent directory,
|
|
19
|
+
* create a symlink from that path to our actual directory.
|
|
20
|
+
*
|
|
21
|
+
* @param currentDir - The directory we're actually running from
|
|
22
|
+
* @param installedPluginsPath - Path to installed_plugins.json (injectable for testing)
|
|
23
|
+
*/
|
|
24
|
+
export function healRegistryMismatch(currentDir, installedPluginsPath) {
|
|
25
|
+
const ipPath = installedPluginsPath ?? resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
26
|
+
if (!existsSync(ipPath))
|
|
27
|
+
return { healed: false, action: "none" };
|
|
28
|
+
if (!existsSync(currentDir))
|
|
29
|
+
return { healed: false, action: "none" };
|
|
30
|
+
let ip;
|
|
31
|
+
try {
|
|
32
|
+
ip = JSON.parse(readFileSync(ipPath, "utf-8"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { healed: false, action: "none" };
|
|
36
|
+
}
|
|
37
|
+
for (const [key, entries] of Object.entries(ip.plugins ?? {})) {
|
|
38
|
+
if (!key.toLowerCase().includes("context-mode"))
|
|
39
|
+
continue;
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const registryPath = entry.installPath;
|
|
42
|
+
if (!registryPath)
|
|
43
|
+
continue;
|
|
44
|
+
// Registry path exists — no healing needed
|
|
45
|
+
if (existsSync(registryPath))
|
|
46
|
+
continue;
|
|
47
|
+
// Registry path doesn't exist — create symlink to our directory
|
|
48
|
+
try {
|
|
49
|
+
const parent = dirname(registryPath);
|
|
50
|
+
if (!existsSync(parent))
|
|
51
|
+
mkdirSync(parent, { recursive: true });
|
|
52
|
+
if (process.platform === "win32") {
|
|
53
|
+
// Windows: use junction (no admin required)
|
|
54
|
+
symlinkSync(currentDir, registryPath, "junction");
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
symlinkSync(currentDir, registryPath);
|
|
58
|
+
}
|
|
59
|
+
return { healed: true, action: "symlink", from: registryPath, to: currentDir };
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return { healed: false, action: "none" };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { healed: false, action: "none" };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Deploy a global SessionStart hook that heals plugin cache mismatches.
|
|
70
|
+
* This hook lives outside the plugin directory, so it survives cache breakage.
|
|
71
|
+
*
|
|
72
|
+
* Written to ~/.claude/hooks/context-mode-cache-heal.sh
|
|
73
|
+
*/
|
|
74
|
+
export function deployGlobalHealHook() {
|
|
75
|
+
const hooksDir = resolve(homedir(), ".claude", "hooks");
|
|
76
|
+
const hookPath = resolve(hooksDir, "context-mode-cache-heal.sh");
|
|
77
|
+
// Already deployed
|
|
78
|
+
if (existsSync(hookPath))
|
|
79
|
+
return { healed: false, action: "none" };
|
|
80
|
+
try {
|
|
81
|
+
if (!existsSync(hooksDir))
|
|
82
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
83
|
+
const script = `#!/usr/bin/env bash
|
|
84
|
+
# context-mode plugin cache self-heal — auto-deployed by context-mode MCP server
|
|
85
|
+
# Fixes anthropics/claude-code#46915: auto-update breaks CLAUDE_PLUGIN_ROOT
|
|
86
|
+
# This hook runs at SessionStart (global, not plugin-level) so it works even
|
|
87
|
+
# when the plugin cache is broken.
|
|
88
|
+
|
|
89
|
+
set -euo pipefail
|
|
90
|
+
|
|
91
|
+
PLUGINS_FILE="$HOME/.claude/plugins/installed_plugins.json"
|
|
92
|
+
[[ -f "$PLUGINS_FILE" ]] || exit 0
|
|
93
|
+
|
|
94
|
+
# Find context-mode entries and heal missing directories
|
|
95
|
+
node -e '
|
|
96
|
+
const fs = require("fs");
|
|
97
|
+
const path = require("path");
|
|
98
|
+
try {
|
|
99
|
+
const ip = JSON.parse(fs.readFileSync(process.argv[1], "utf-8"));
|
|
100
|
+
for (const [key, entries] of Object.entries(ip.plugins || {})) {
|
|
101
|
+
if (!key.toLowerCase().includes("context-mode")) continue;
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const p = entry.installPath;
|
|
104
|
+
if (!p || fs.existsSync(p)) continue;
|
|
105
|
+
const parent = path.dirname(p);
|
|
106
|
+
if (!fs.existsSync(parent)) continue;
|
|
107
|
+
const dirs = fs.readdirSync(parent).filter(d => /^\\d+\\.\\d+/.test(d) && fs.statSync(path.join(parent, d)).isDirectory());
|
|
108
|
+
if (dirs.length === 0) continue;
|
|
109
|
+
dirs.sort((a, b) => {
|
|
110
|
+
const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
|
|
111
|
+
for (let i = 0; i < 3; i++) { if ((pa[i]||0) !== (pb[i]||0)) return (pa[i]||0) - (pb[i]||0); }
|
|
112
|
+
return 0;
|
|
113
|
+
});
|
|
114
|
+
const target = path.join(parent, dirs[dirs.length - 1]);
|
|
115
|
+
try { fs.symlinkSync(target, p); } catch {}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch {}
|
|
119
|
+
' "$PLUGINS_FILE" 2>/dev/null || true
|
|
120
|
+
`;
|
|
121
|
+
writeFileSync(hookPath, script, { mode: 0o755 });
|
|
122
|
+
return { healed: true, action: "global-hook", from: hookPath };
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return { healed: false, action: "none" };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Backward symlink: during postinstall, if the registry points to a
|
|
130
|
+
* non-existent OLD path, create a symlink from old → new (our directory).
|
|
131
|
+
* Same as healRegistryMismatch but called from postinstall context.
|
|
132
|
+
*/
|
|
133
|
+
export { healRegistryMismatch as healBackwardCompat };
|
|
134
|
+
/** One-shot flag for mid-session heal in server.ts */
|
|
135
|
+
let _midSessionHealed = false;
|
|
136
|
+
/**
|
|
137
|
+
* Mid-session heal — call on first MCP tool invocation.
|
|
138
|
+
* Checks if registry path differs from our running directory.
|
|
139
|
+
* Creates symlink if needed. Runs only once per process.
|
|
140
|
+
*/
|
|
141
|
+
export function healMidSession(currentDir) {
|
|
142
|
+
if (_midSessionHealed)
|
|
143
|
+
return { healed: false, action: "none" };
|
|
144
|
+
_midSessionHealed = true;
|
|
145
|
+
return healRegistryMismatch(currentDir);
|
|
146
|
+
}
|
|
147
|
+
/** Reset mid-session flag (for testing only) */
|
|
148
|
+
export function _resetMidSession() {
|
|
149
|
+
_midSessionHealed = false;
|
|
150
|
+
}
|
package/build/cli.js
CHANGED
|
@@ -62,6 +62,7 @@ const HOOK_MAP = {
|
|
|
62
62
|
precompact: "hooks/precompact.mjs",
|
|
63
63
|
sessionstart: "hooks/sessionstart.mjs",
|
|
64
64
|
userpromptsubmit: "hooks/userpromptsubmit.mjs",
|
|
65
|
+
stop: "hooks/stop.mjs",
|
|
65
66
|
},
|
|
66
67
|
"gemini-cli": {
|
|
67
68
|
beforeagent: "hooks/gemini-cli/beforeagent.mjs",
|
|
@@ -101,6 +102,22 @@ const HOOK_MAP = {
|
|
|
101
102
|
precompact: "hooks/jetbrains-copilot/precompact.mjs",
|
|
102
103
|
sessionstart: "hooks/jetbrains-copilot/sessionstart.mjs",
|
|
103
104
|
},
|
|
105
|
+
"copilot-cli": {
|
|
106
|
+
pretooluse: "hooks/copilot-cli/pretooluse.mjs",
|
|
107
|
+
posttooluse: "hooks/copilot-cli/posttooluse.mjs",
|
|
108
|
+
precompact: "hooks/copilot-cli/precompact.mjs",
|
|
109
|
+
sessionstart: "hooks/copilot-cli/sessionstart.mjs",
|
|
110
|
+
userpromptsubmit: "hooks/copilot-cli/userpromptsubmit.mjs",
|
|
111
|
+
stop: "hooks/copilot-cli/stop.mjs",
|
|
112
|
+
},
|
|
113
|
+
// Antigravity CLI (`agy`) — bounded PreToolUse enforcement plus capture-only
|
|
114
|
+
// PostToolUse/Stop hooks. Configured via an installed agy plugin's
|
|
115
|
+
// hooks/hooks.json or ~/.gemini/config/hooks.json.
|
|
116
|
+
"antigravity-cli": {
|
|
117
|
+
pretooluse: "hooks/antigravity-cli/pretooluse.mjs",
|
|
118
|
+
posttooluse: "hooks/antigravity-cli/posttooluse.mjs",
|
|
119
|
+
stop: "hooks/antigravity-cli/stop.mjs",
|
|
120
|
+
},
|
|
104
121
|
"kimi": {
|
|
105
122
|
pretooluse: "hooks/kimi/pretooluse.mjs",
|
|
106
123
|
posttooluse: "hooks/kimi/posttooluse.mjs",
|
|
@@ -132,7 +149,16 @@ async function hookDispatch(platform, event) {
|
|
|
132
149
|
}
|
|
133
150
|
const scriptPath = HOOK_MAP[platform]?.[event];
|
|
134
151
|
if (!scriptPath) {
|
|
135
|
-
|
|
152
|
+
// Fail OPEN. context-mode has no hook for this platform/event — most often
|
|
153
|
+
// because a newer adapter's hook command (`context-mode hook copilot-cli …`)
|
|
154
|
+
// is running against an OLDER global binary that predates that adapter
|
|
155
|
+
// (version skew). Exit 0 (no decision) so the host ALLOWS the tool. Exiting
|
|
156
|
+
// non-zero here makes some hosts treat it as a hook ERROR and DENY the tool:
|
|
157
|
+
// verified against GitHub Copilot CLI 1.0.59, where an exit-1 + empty-stdout
|
|
158
|
+
// PreToolUse hook blocks EVERY tool ("Denied by preToolUse hook (hook
|
|
159
|
+
// errored)") — bricking the agent during a skew instead of just disabling
|
|
160
|
+
// context-mode's instrumentation.
|
|
161
|
+
process.exit(0);
|
|
136
162
|
}
|
|
137
163
|
const pluginRoot = getPluginRoot();
|
|
138
164
|
await import(pathToFileURL(join(pluginRoot, scriptPath)).href);
|
|
@@ -205,7 +231,7 @@ else if (args[0] === "hook") {
|
|
|
205
231
|
hookDispatch(args[1], args[2]);
|
|
206
232
|
}
|
|
207
233
|
else if (args[0] === "insight") {
|
|
208
|
-
insight(
|
|
234
|
+
insight();
|
|
209
235
|
}
|
|
210
236
|
else if (args[0] === "statusline") {
|
|
211
237
|
// Status line implementation lives in bin/statusline.mjs to keep it
|
|
@@ -1030,102 +1056,16 @@ async function doctor() {
|
|
|
1030
1056
|
return 0;
|
|
1031
1057
|
}
|
|
1032
1058
|
/* -------------------------------------------------------
|
|
1033
|
-
* Insight — analytics dashboard
|
|
1059
|
+
* Insight — hosted analytics dashboard
|
|
1034
1060
|
* ------------------------------------------------------- */
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const sessDir = ensureWritableStorageDir(resolveSessionStorageDir(() => adapter.getSessionDir()));
|
|
1044
|
-
const contentDir = ensureWritableStorageDir(resolveContentStorageDir(() => sessDir));
|
|
1045
|
-
const cacheDir = join(dirname(sessDir), "insight-cache");
|
|
1046
|
-
if (!existsSync(join(insightSource, "server.mjs"))) {
|
|
1047
|
-
console.error("Error: Insight source not found. Try upgrading context-mode.");
|
|
1048
|
-
process.exit(1);
|
|
1049
|
-
}
|
|
1050
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
1051
|
-
// Copy source if newer
|
|
1052
|
-
const srcMtime = statSync(join(insightSource, "server.mjs")).mtimeMs;
|
|
1053
|
-
const cacheMtime = existsSync(join(cacheDir, "server.mjs"))
|
|
1054
|
-
? statSync(join(cacheDir, "server.mjs")).mtimeMs : 0;
|
|
1055
|
-
if (srcMtime > cacheMtime) {
|
|
1056
|
-
console.log("Copying Insight source...");
|
|
1057
|
-
cpSync(insightSource, cacheDir, { recursive: true, force: true });
|
|
1058
|
-
}
|
|
1059
|
-
// Install deps
|
|
1060
|
-
if (!existsSync(join(cacheDir, "node_modules"))) {
|
|
1061
|
-
console.log("Installing dependencies (first run)...");
|
|
1062
|
-
try {
|
|
1063
|
-
npmExec("npm install --production=false", { cwd: cacheDir, stdio: "inherit", timeout: 300000 });
|
|
1064
|
-
}
|
|
1065
|
-
catch {
|
|
1066
|
-
// Clean up partial install so next run retries fresh
|
|
1067
|
-
try {
|
|
1068
|
-
rmSync(join(cacheDir, "node_modules"), { recursive: true, force: true });
|
|
1069
|
-
}
|
|
1070
|
-
catch { }
|
|
1071
|
-
throw new Error("npm install failed — please retry");
|
|
1072
|
-
}
|
|
1073
|
-
// Sentinel check: verify install completed (cold cache can timeout leaving partial node_modules)
|
|
1074
|
-
if (!existsSync(join(cacheDir, "node_modules", "vite")) || !existsSync(join(cacheDir, "node_modules", "better-sqlite3"))) {
|
|
1075
|
-
rmSync(join(cacheDir, "node_modules"), { recursive: true, force: true });
|
|
1076
|
-
throw new Error("npm install incomplete — please retry");
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
// Build
|
|
1080
|
-
console.log("Building dashboard...");
|
|
1081
|
-
execSync("npx vite build", { cwd: cacheDir, stdio: "pipe", timeout: 60000 });
|
|
1082
|
-
// Start server
|
|
1083
|
-
const url = `http://localhost:${port}`;
|
|
1084
|
-
console.log(`\n context-mode Insight\n ${url}\n`);
|
|
1085
|
-
const child = spawn("node", [join(cacheDir, "server.mjs")], {
|
|
1086
|
-
cwd: cacheDir,
|
|
1087
|
-
env: {
|
|
1088
|
-
...process.env,
|
|
1089
|
-
PORT: String(port),
|
|
1090
|
-
INSIGHT_SESSION_DIR: sessDir,
|
|
1091
|
-
INSIGHT_CONTENT_DIR: contentDir,
|
|
1092
|
-
},
|
|
1093
|
-
stdio: "inherit",
|
|
1094
|
-
});
|
|
1095
|
-
child.on("error", () => { }); // prevent unhandled error crash
|
|
1096
|
-
// Wait for server to be ready, then verify it started
|
|
1097
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
1098
|
-
try {
|
|
1099
|
-
const { request } = await import("node:http");
|
|
1100
|
-
await new Promise((resolve, reject) => {
|
|
1101
|
-
const req = request(`http://127.0.0.1:${port}/api/overview`, { timeout: 3000 }, (res) => {
|
|
1102
|
-
resolve();
|
|
1103
|
-
res.resume();
|
|
1104
|
-
});
|
|
1105
|
-
req.on("error", reject);
|
|
1106
|
-
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
|
|
1107
|
-
req.end();
|
|
1108
|
-
});
|
|
1109
|
-
}
|
|
1110
|
-
catch {
|
|
1111
|
-
console.error(`\nError: Port ${port} appears to be in use. Either a previous dashboard is still running, or another service is using this port.`);
|
|
1112
|
-
console.error(`\nTo fix:`);
|
|
1113
|
-
console.error(` Kill the existing process: ${process.platform === "win32" ? `netstat -ano | findstr :${port}` : `lsof -ti:${port} | xargs kill`}`);
|
|
1114
|
-
console.error(` Or use a different port: context-mode insight ${port + 1}`);
|
|
1115
|
-
child.kill();
|
|
1116
|
-
process.exit(1);
|
|
1117
|
-
}
|
|
1118
|
-
// Open browser — execFile with arg array, no shell interpolation.
|
|
1119
|
-
openInBrowser(url);
|
|
1120
|
-
// Keep alive until Ctrl+C
|
|
1121
|
-
process.on("SIGINT", () => { child.kill(); process.exit(0); });
|
|
1122
|
-
process.on("SIGTERM", () => { child.kill(); process.exit(0); });
|
|
1123
|
-
}
|
|
1124
|
-
catch (err) {
|
|
1125
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1126
|
-
console.error(`\nInsight error: ${msg}`);
|
|
1127
|
-
process.exit(1);
|
|
1128
|
-
}
|
|
1061
|
+
// Insight pivoted from a locally-built dashboard to the hosted product at
|
|
1062
|
+
// context-mode.com/insight (the landing page is the single source of truth).
|
|
1063
|
+
// The command now just opens that URL in the default browser.
|
|
1064
|
+
async function insight() {
|
|
1065
|
+
const url = "https://context-mode.com/insight";
|
|
1066
|
+
console.log(`\n context-mode Insight\n ${url}\n`);
|
|
1067
|
+
// Open browser — execFile with arg array, no shell interpolation.
|
|
1068
|
+
openInBrowser(url);
|
|
1129
1069
|
}
|
|
1130
1070
|
/* -------------------------------------------------------
|
|
1131
1071
|
* Upgrade — adapter-aware hook configuration
|
package/build/executor.d.ts
CHANGED
|
@@ -13,6 +13,31 @@ export declare function buildSpawnOptions(platform: NodeJS.Platform): {
|
|
|
13
13
|
};
|
|
14
14
|
/** Pure helper — exported for unit testing. Restores parent PATH after shell startup. */
|
|
15
15
|
export declare function buildShellScriptContent(code: string, inheritedPath: string | undefined, platform: NodeJS.Platform): string;
|
|
16
|
+
export declare function buildPowerShellScriptContent(code: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Pure helper — exported for unit testing. Issue #782.
|
|
19
|
+
*
|
|
20
|
+
* On Windows, the sandbox shell runtime is Git Bash. A bare `mvn` invocation
|
|
21
|
+
* runs Maven's POSIX shell script, which on the `mingw=true` branch (uname →
|
|
22
|
+
* MINGW64_NT-*) fails to convert `CLASSWORLDS_JAR` from a POSIX path
|
|
23
|
+
* (`/c/tools/maven/boot/plexus-classworlds-*.jar`) to a Windows path. Native
|
|
24
|
+
* `java.exe` then can't resolve the bootstrap jar → ClassNotFoundException for
|
|
25
|
+
* `org.codehaus.plexus.classworlds.launcher.Launcher`.
|
|
26
|
+
*
|
|
27
|
+
* The third-way fix (issue Option C): rewrite the bare `mvn` token to `mvn.cmd`,
|
|
28
|
+
* the native Windows launcher that uses Windows-native paths and bypasses the
|
|
29
|
+
* broken mingw shell branch entirely. This does NOT touch the global MSYS
|
|
30
|
+
* path-conversion env (MSYS_NO_PATHCONV / MSYS2_ARG_CONV_EXCL), which #826/#791
|
|
31
|
+
* deliberately leave unset so native git.exe launched from bash keeps its
|
|
32
|
+
* /tmp→C:\ argument conversion. Re-enabling global suppression would re-break
|
|
33
|
+
* native git; rewriting only the mvn token keeps both correct.
|
|
34
|
+
*
|
|
35
|
+
* Only a `mvn` that starts a command (start of string, or after a shell
|
|
36
|
+
* separator `&& | ; ( newline`) is rewritten. `mvnw`, `mvnd`, `mymvn`,
|
|
37
|
+
* paths like `./mvnw`, and an already-`mvn.cmd` token are left untouched
|
|
38
|
+
* (the token must be exactly `mvn` followed by whitespace or end-of-string).
|
|
39
|
+
*/
|
|
40
|
+
export declare function rewriteWindowsBuildTools(code: string, platform: NodeJS.Platform): string;
|
|
16
41
|
interface ExecuteOptions {
|
|
17
42
|
language: Language;
|
|
18
43
|
code: string;
|
package/build/executor.js
CHANGED
|
@@ -55,6 +55,21 @@ export function buildShellScriptContent(code, inheritedPath, platform) {
|
|
|
55
55
|
return code;
|
|
56
56
|
return `export PATH=${quoteForPosixShell(inheritedPath)}\n${code}`;
|
|
57
57
|
}
|
|
58
|
+
function isPowerShell(shellPath) {
|
|
59
|
+
const shellName = shellPath?.toLowerCase() ?? "";
|
|
60
|
+
return shellName.includes("powershell") || shellName.includes("pwsh");
|
|
61
|
+
}
|
|
62
|
+
export function buildPowerShellScriptContent(code) {
|
|
63
|
+
// Prefix a UTF-8 BOM so Windows PowerShell 5.1 reliably detects the script
|
|
64
|
+
// file as UTF-8 (without it, 5.1 falls back to the ANSI code page and
|
|
65
|
+
// mangles non-ASCII characters in the script body).
|
|
66
|
+
return [
|
|
67
|
+
"\uFEFF[Console]::InputEncoding = [System.Text.UTF8Encoding]::new()",
|
|
68
|
+
"[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()",
|
|
69
|
+
"$OutputEncoding = [System.Text.UTF8Encoding]::new()",
|
|
70
|
+
code,
|
|
71
|
+
].join("\n");
|
|
72
|
+
}
|
|
58
73
|
/**
|
|
59
74
|
* Resolve the real OS temp directory, bypassing any TMPDIR env override.
|
|
60
75
|
* os.tmpdir() reads TMPDIR from the environment, which some shells/tools
|
|
@@ -72,6 +87,86 @@ const OS_TMPDIR = (() => {
|
|
|
72
87
|
catch { /* fall through */ }
|
|
73
88
|
return "/tmp";
|
|
74
89
|
})();
|
|
90
|
+
/**
|
|
91
|
+
* Pure helper — exported for unit testing. Issue #782.
|
|
92
|
+
*
|
|
93
|
+
* On Windows, the sandbox shell runtime is Git Bash. A bare `mvn` invocation
|
|
94
|
+
* runs Maven's POSIX shell script, which on the `mingw=true` branch (uname →
|
|
95
|
+
* MINGW64_NT-*) fails to convert `CLASSWORLDS_JAR` from a POSIX path
|
|
96
|
+
* (`/c/tools/maven/boot/plexus-classworlds-*.jar`) to a Windows path. Native
|
|
97
|
+
* `java.exe` then can't resolve the bootstrap jar → ClassNotFoundException for
|
|
98
|
+
* `org.codehaus.plexus.classworlds.launcher.Launcher`.
|
|
99
|
+
*
|
|
100
|
+
* The third-way fix (issue Option C): rewrite the bare `mvn` token to `mvn.cmd`,
|
|
101
|
+
* the native Windows launcher that uses Windows-native paths and bypasses the
|
|
102
|
+
* broken mingw shell branch entirely. This does NOT touch the global MSYS
|
|
103
|
+
* path-conversion env (MSYS_NO_PATHCONV / MSYS2_ARG_CONV_EXCL), which #826/#791
|
|
104
|
+
* deliberately leave unset so native git.exe launched from bash keeps its
|
|
105
|
+
* /tmp→C:\ argument conversion. Re-enabling global suppression would re-break
|
|
106
|
+
* native git; rewriting only the mvn token keeps both correct.
|
|
107
|
+
*
|
|
108
|
+
* Only a `mvn` that starts a command (start of string, or after a shell
|
|
109
|
+
* separator `&& | ; ( newline`) is rewritten. `mvnw`, `mvnd`, `mymvn`,
|
|
110
|
+
* paths like `./mvnw`, and an already-`mvn.cmd` token are left untouched
|
|
111
|
+
* (the token must be exactly `mvn` followed by whitespace or end-of-string).
|
|
112
|
+
*/
|
|
113
|
+
export function rewriteWindowsBuildTools(code, platform) {
|
|
114
|
+
if (platform !== "win32")
|
|
115
|
+
return code;
|
|
116
|
+
// Rewrite a bare `mvn` command token to `mvn.cmd` (Maven's native Windows launcher).
|
|
117
|
+
// Algorithmic (no regex): only at a command-start position (string start or right
|
|
118
|
+
// after a shell separator ; & | ( newline, skipping leading spaces/tabs) and only
|
|
119
|
+
// when the token is exactly `mvn` followed by whitespace or end — leaves
|
|
120
|
+
// mvnw / mvnd / ./mvnw / already-mvn.cmd untouched.
|
|
121
|
+
const SEP = new Set([";", "&", "|", "(", "\n"]);
|
|
122
|
+
let out = "";
|
|
123
|
+
let atStart = true;
|
|
124
|
+
let i = 0;
|
|
125
|
+
while (i < code.length) {
|
|
126
|
+
const ch = code[i];
|
|
127
|
+
if (atStart && (ch === " " || ch === "\t")) {
|
|
128
|
+
out += ch;
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (atStart && code.startsWith("mvn", i)) {
|
|
133
|
+
const after = code[i + 3];
|
|
134
|
+
if (after === undefined || after === " " || after === "\t" || after === "\n") {
|
|
135
|
+
out += "mvn.cmd";
|
|
136
|
+
i += 3;
|
|
137
|
+
atStart = false;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
out += ch;
|
|
142
|
+
atStart = SEP.has(ch);
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Remove a sandbox temp dir, retrying on Windows. Issue #788.
|
|
149
|
+
*
|
|
150
|
+
* On Windows, a child process that opened SQLite databases inside the sandbox
|
|
151
|
+
* can leave `*-wal` / `*-shm` files with handles that linger briefly after the
|
|
152
|
+
* process exits. A single `rmSync` then throws EBUSY/EPERM/ENOTEMPTY and the
|
|
153
|
+
* old silent `catch {}` swallowed it, leaking `.ctx-mode-*` directories under
|
|
154
|
+
* `%TEMP%`. Node's `rmSync({ maxRetries, retryDelay })` is purpose-built for
|
|
155
|
+
* exactly this Windows-handle race, so let it back off and retry.
|
|
156
|
+
*/
|
|
157
|
+
function cleanupTmpDir(tmpDir) {
|
|
158
|
+
try {
|
|
159
|
+
rmSync(tmpDir, {
|
|
160
|
+
recursive: true,
|
|
161
|
+
force: true,
|
|
162
|
+
maxRetries: isWin ? 8 : 2,
|
|
163
|
+
retryDelay: 100,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
/* best-effort — OS will reclaim %TEMP% eventually */
|
|
168
|
+
}
|
|
169
|
+
}
|
|
75
170
|
/** Kill process tree — on Windows uses taskkill /T; on Unix kills the process group. */
|
|
76
171
|
function killTree(proc) {
|
|
77
172
|
if (isWin && proc.pid) {
|
|
@@ -142,29 +237,27 @@ export class PolyglotExecutor {
|
|
|
142
237
|
if (cmd[0] === "__rust_compile_run__") {
|
|
143
238
|
return await this.#compileAndRun(filePath, tmpDir, timeout);
|
|
144
239
|
}
|
|
145
|
-
//
|
|
146
|
-
// and other project-aware tools
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
240
|
+
// Every language runs in the project directory so git, relative paths,
|
|
241
|
+
// and other project-aware tools resolve naturally. The script FILE lives
|
|
242
|
+
// in the sandbox tmpDir and is passed to the runtime by absolute path
|
|
243
|
+
// (see buildCommand), so cwd is free to be the project root.
|
|
244
|
+
//
|
|
245
|
+
// Issue #788 — previously only `shell` used the project root; non-shell
|
|
246
|
+
// runtimes (python/js/ts/…) used tmpDir, so repo-relative checks like
|
|
247
|
+
// `pathlib.Path("package.json").exists()` silently failed depending on
|
|
248
|
+
// the chosen language. Unifying cwd removes that surprise.
|
|
249
|
+
// Issue #45 — `cwdOverride` lets per-call sites (Codex MCP handlers) pin
|
|
250
|
+
// cwd without mutating process-wide state.
|
|
251
|
+
const cwd = cwdOverride ?? this.#projectRoot;
|
|
153
252
|
const result = await this.#spawn(cmd, cwd, tmpDir, timeout, background);
|
|
154
253
|
// Skip tmpDir cleanup if process was backgrounded — it may still need files
|
|
155
254
|
if (!result.backgrounded) {
|
|
156
|
-
|
|
157
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
158
|
-
}
|
|
159
|
-
catch { /* ignore */ }
|
|
255
|
+
cleanupTmpDir(tmpDir);
|
|
160
256
|
}
|
|
161
257
|
return result;
|
|
162
258
|
}
|
|
163
259
|
catch (err) {
|
|
164
|
-
|
|
165
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
166
|
-
}
|
|
167
|
-
catch { /* ignore */ }
|
|
260
|
+
cleanupTmpDir(tmpDir);
|
|
168
261
|
throw err;
|
|
169
262
|
}
|
|
170
263
|
}
|
|
@@ -190,7 +283,15 @@ export class PolyglotExecutor {
|
|
|
190
283
|
}
|
|
191
284
|
const fp = join(tmpDir, buildScriptFilename(language, process.platform, language === "shell" ? this.#runtimes.shell : null));
|
|
192
285
|
if (language === "shell") {
|
|
193
|
-
|
|
286
|
+
const shellPath = this.#runtimes.shell;
|
|
287
|
+
// #782 — on Windows Git Bash, rewrite bare `mvn` → `mvn.cmd` so Maven
|
|
288
|
+
// uses its native Windows launcher (correct path handling) instead of
|
|
289
|
+
// the broken mingw shell branch. No-op on non-Windows.
|
|
290
|
+
const rewritten = rewriteWindowsBuildTools(code, process.platform);
|
|
291
|
+
const shellCode = isWin && isPowerShell(shellPath)
|
|
292
|
+
? buildPowerShellScriptContent(rewritten)
|
|
293
|
+
: rewritten;
|
|
294
|
+
writeFileSync(fp, buildShellScriptContent(shellCode, process.env.PATH, process.platform), { encoding: "utf-8", mode: 0o700 });
|
|
194
295
|
}
|
|
195
296
|
else {
|
|
196
297
|
writeFileSync(fp, code, "utf-8");
|
|
@@ -289,8 +390,20 @@ export class PolyglotExecutor {
|
|
|
289
390
|
if (proc.pid)
|
|
290
391
|
this.#backgroundedPids.add(proc.pid);
|
|
291
392
|
proc.unref();
|
|
292
|
-
|
|
293
|
-
|
|
393
|
+
// Do NOT destroy stdout/stderr — closing the read end of the pipe
|
|
394
|
+
// sends SIGPIPE to the child on its next write, killing it.
|
|
395
|
+
// Instead, replace the data listeners with no-op drains that
|
|
396
|
+
// consume the stream without accumulating buffers. This keeps
|
|
397
|
+
// the pipe open and prevents the child from blocking on a full
|
|
398
|
+
// pipe buffer.
|
|
399
|
+
if (proc.stdout) {
|
|
400
|
+
proc.stdout.removeAllListeners("data");
|
|
401
|
+
proc.stdout.on("data", () => { });
|
|
402
|
+
}
|
|
403
|
+
if (proc.stderr) {
|
|
404
|
+
proc.stderr.removeAllListeners("data");
|
|
405
|
+
proc.stderr.on("data", () => { });
|
|
406
|
+
}
|
|
294
407
|
const rawStdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
295
408
|
const rawStderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
296
409
|
res({
|
|
@@ -493,10 +606,18 @@ export class PolyglotExecutor {
|
|
|
493
606
|
if (!env["PATH"]) {
|
|
494
607
|
env["PATH"] = isWin ? "" : "/usr/local/bin:/usr/bin:/bin";
|
|
495
608
|
}
|
|
496
|
-
// Windows-critical
|
|
609
|
+
// Windows-critical PATH fixes.
|
|
497
610
|
if (isWin) {
|
|
498
|
-
|
|
499
|
-
|
|
611
|
+
// Do not carry global MSYS path-conversion blockers into Git Bash.
|
|
612
|
+
// Native Windows tools launched from bash (notably git.exe) need MSYS
|
|
613
|
+
// to convert /tmp-style arguments to Windows paths so sibling tools see
|
|
614
|
+
// the same filesystem location (#791).
|
|
615
|
+
for (const key of Object.keys(env)) {
|
|
616
|
+
const upper = key.toUpperCase();
|
|
617
|
+
if (upper === "MSYS_NO_PATHCONV" || upper === "MSYS2_ARG_CONV_EXCL") {
|
|
618
|
+
delete env[key];
|
|
619
|
+
}
|
|
620
|
+
}
|
|
500
621
|
const gitUsrBin = "C:\\Program Files\\Git\\usr\\bin";
|
|
501
622
|
const gitBin = "C:\\Program Files\\Git\\bin";
|
|
502
623
|
if (!env["PATH"].includes(gitUsrBin)) {
|
package/build/opencode-plugin.js
CHANGED
|
@@ -179,7 +179,7 @@ async function createContextModePlugin(ctx) {
|
|
|
179
179
|
const toolInput = output.args ?? {};
|
|
180
180
|
let decision;
|
|
181
181
|
try {
|
|
182
|
-
decision = routing.routePreToolUse(toolName, toolInput, projectDir,
|
|
182
|
+
decision = routing.routePreToolUse(toolName, toolInput, projectDir, platform);
|
|
183
183
|
}
|
|
184
184
|
catch {
|
|
185
185
|
return; // Routing failure → allow passthrough
|
|
@@ -194,7 +194,10 @@ async function createContextModePlugin(ctx) {
|
|
|
194
194
|
// Mutate output.args — OpenCode reads the mutated output object
|
|
195
195
|
Object.assign(output.args, decision.updatedInput);
|
|
196
196
|
}
|
|
197
|
-
|
|
197
|
+
if (decision.action === "context" && decision.additionalContext) {
|
|
198
|
+
// Mutate output.args — OpenCode reads the mutated output object
|
|
199
|
+
output.args.additionalContext = decision.additionalContext;
|
|
200
|
+
}
|
|
198
201
|
},
|
|
199
202
|
// ── PostToolUse: Session event capture ──────────────
|
|
200
203
|
"tool.execute.after": async (input, output) => {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolNamer } from "./tool-naming.js";
|
|
2
|
+
export interface RoutingBlockOptions {
|
|
3
|
+
includeCommands?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function createRoutingBlock(t: ToolNamer, options?: RoutingBlockOptions): string;
|
|
6
|
+
export declare function createReadGuidance(t: ToolNamer): string;
|
|
7
|
+
export declare function createGrepGuidance(t: ToolNamer): string;
|
|
8
|
+
export declare function createBashGuidance(t: ToolNamer): string;
|