Compare commits

...
Author SHA1 Message Date
esengine d5ee56c1b7 fix(cli): every mcp subcommand prints usage for -h/--help and mcp import refuses arguments
Only add intercepted the flag, so mcp import --help ran the import and mcp remove --help looked up a server named --help. The dispatcher now answers the flag once, before choosing a subcommand; a flag later on the line still reaches the command, as in mcp add name -- cmd --help.
2026-11-08 03:31:56 -08:00
esengine 45e7f52d70 fix(electron): the instance probe and the kernel launch hide their console window on Windows
instance.js ran the console-subsystem kernel with execFileSync and no windowsHide on every launch, before any window exists, which is the console flash in #15016. host.js spawned the same binary without it too. packagegrants.js and cua.js already pass windowsHide; the probe now takes the runner as an argument so a test can see the option.
2026-11-07 20:31:50 -08:00
7 changed files with 72 additions and 8 deletions

No files matched your search

+1 -1
View File
@@ -28,7 +28,7 @@ class HostStartError extends Error {
function start(binary, args, { onStderr, onExit, onAct, onSlow, systemLanguage, timeoutMs = HANDSHAKE_TIMEOUT_MS } = {}) {
const env = { ...process.env };
if (systemLanguage) env.REASONIX_SYSTEM_LANG = systemLanguage;
const child = spawn(binary, args, { stdio: ["pipe", "pipe", "pipe"], env });
const child = spawn(binary, args, { stdio: ["pipe", "pipe", "pipe"], env, windowsHide: true });
child.stderr.setEncoding("utf8");
if (onStderr) child.stderr.on("data", onStderr);
if (onExit) child.on("exit", onExit);
+2 -2
View File
@@ -6,9 +6,9 @@ const { execFileSync } = require("node:child_process");
// one Studio, canonicalized where it lives. The kernel answers it, because the
// home is resolved from an environment and a platform default this process
// does not own.
function instanceID(binary) {
function instanceID(binary, run = execFileSync) {
try {
return execFileSync(binary, ["-instance-id"], { encoding: "utf8", timeout: 10000 }).trim();
return run(binary, ["-instance-id"], { encoding: "utf8", timeout: 10000, windowsHide: true }).trim();
} catch {
// A kernel that cannot be run at all is a launch that is going to fail with
// a better message in a moment. Refusing to start here would report it as
+9 -1
View File
@@ -625,7 +625,7 @@ test("Windows is given the format it reads the small sizes from", () => {
assert.equal(iconFile("linux"), "icon.png");
});
const { profileFor } = require("../src/instance.js");
const { profileFor, instanceID } = require("../src/instance.js");
// Two homes are two Studios, and the profile is what carries that into the
// platform's own lock. Sharing one would make the second launch look like a
@@ -641,6 +641,14 @@ test("each instance gets a profile of its own", () => {
assert.equal(profileFor(base, ""), base);
});
test("the instance probe runs the kernel without a console window of its own", () => {
let options;
const run = (binary, args, opts) => { options = { binary, args, opts }; return "abc123\n"; };
assert.equal(instanceID("host", run), "abc123");
assert.deepEqual(options.args, ["-instance-id"]);
assert.equal(options.opts.windowsHide, true);
});
// A program that cannot be started emits "error" and never "exit". Waiting for
// the handshake instead spent the full timeout and then blamed the kernel for
// saying nothing, which is where the launch failure on Windows hid: go build
+8 -4
View File
@@ -33,6 +33,10 @@ func mcpCommand(args []string) int {
mcpUsage()
return 2
}
if len(args) > 1 && (args[1] == "-h" || args[1] == "--help") {
mcpUsage()
return 0
}
switch args[0] {
case "list", "ls":
if len(args) > 1 {
@@ -41,10 +45,6 @@ func mcpCommand(args []string) int {
}
return mcpList()
case "add":
if len(args) > 1 && (args[1] == "-h" || args[1] == "--help") {
mcpUsage()
return 0
}
return mcpAddCLI(args[1:])
case "get":
return mcpGetCLI(args[1:])
@@ -64,6 +64,10 @@ func mcpCommand(args []string) int {
case "update":
return mcpUpdateCLI(args[1:])
case "import":
if len(args) > 1 {
fmt.Fprintf(os.Stderr, "mcp import: unexpected argument %q\n", args[1])
return 2
}
return mcpImportCLI()
case "browse", "search":
return mcpBrowseCLI(args[1:])
+32
View File
@@ -33,3 +33,35 @@ func TestMCPListAndGetRejectExtraArguments(t *testing.T) {
}
}
}
func TestMCPHelpFlagPrintsUsageForEverySubcommandAndRunsNothing(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
t.Setenv("REASONIX_HOME", home)
for _, sub := range []string{"list", "ls", "add", "get", "remove", "rm", "enable", "disable", "retry", "connect", "trust", "auth", "authorize", "update", "import", "browse", "search", "install"} {
for _, flag := range []string{"--help", "-h"} {
var code int
stdout, stderr := captureCLIOutput(t, func() {
code = Run([]string{"mcp", sub, flag}, "test")
})
if code != 0 || !strings.Contains(stdout, "reasonix mcp") || stderr != "" || strings.Contains(stdout, "imported") {
t.Errorf("mcp %s %s: exit %d, want 0 with usage only\nstdout:\n%s\nstderr:\n%s", sub, flag, code, stdout, stderr)
}
}
}
}
func TestMCPImportRejectsArgumentsInsteadOfImporting(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
t.Setenv("REASONIX_HOME", home)
var code int
stdout, stderr := captureCLIOutput(t, func() {
code = Run([]string{"mcp", "import", "--nope"}, "test")
})
if code != 2 || stdout != "" || stderr == "" {
t.Fatalf("mcp import --nope exited %d, want 2 with an error\nstdout:\n%s\nstderr:\n%s", code, stdout, stderr)
}
}
@@ -0,0 +1,10 @@
## 修复
- 修复命令行里 `reasonix mcp import --help`(以及 `mcp remove --help` 等其他子命令)不会显示用法,反而把 `--help` 当成参数真的去执行:`import` 会直接改写 MCP 配置。现在所有 `reasonix mcp` 子命令的 `--help` / `-h` 都只打印用法并退出,什么都不改;`mcp import` 带多余参数时会报错而不是照常导入(#15022)。
---
<!-- lang:en -->
## Fixed
- `reasonix mcp import --help` (and `--help` on the other `mcp` subcommands) did not print usage and ran the command with `--help` as an argument, so `import` rewrote the MCP configuration. Every `reasonix mcp` subcommand now prints usage for `--help` / `-h` and changes nothing, and `mcp import` refuses extra arguments instead of importing (#15022).
@@ -0,0 +1,10 @@
## 修复
- 修复 Windows 上每次启动 Reasonix Studio 都会先闪一下黑色控制台窗口的问题:启动时用来确认"是不是同一个 Studio"的内核探测,以及启动内核本身,现在都隐藏控制台窗口(#15016)。
---
<!-- lang:en -->
## Fixed
- On Windows, launching Reasonix Studio no longer flashes a console window first: the kernel probe that tells whether this is the same Studio, and the kernel launch itself, now run with their console hidden (#15016).