gm-skill 2.0.1913 → 2.0.1914

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1913",
3
+ "version": "2.0.1914",
4
4
  "description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -861,7 +861,19 @@ function findBrowserRunner() {
861
861
  // above), it just skips the registry round-trip for the tag lookup itself.
862
862
  const cachedVersion = findCachedBunRunnerVersion();
863
863
  const pkgSpec = cachedVersion ? `${BROWSER_RUNNER_BIN}@${cachedVersion}` : `${BROWSER_RUNNER_BIN}@latest`;
864
- return { cmd: 'bun', baseArgs: ['x', pkgSpec], shell: true };
864
+ // bun is a real native binary (resolved to its actual path via `where`/`which` just above), so
865
+ // it can be spawned directly without an intermediate shell. shell:true here was forcing every
866
+ // browser-verb -e script argument through cmd.exe's argv parsing on Windows, which treats an
867
+ // unescaped `&` (extremely common in real target URLs, e.g. `?singleplayer&world=...` query
868
+ // strings) as a command separator EVEN INSIDE A QUOTED ARGUMENT -- silently truncating the
869
+ // script/URL mid-string, corrupting the executed code (observed live: "await page.goto(\"http:
870
+ // //host/path?a" with everything from the `&` onward missing, then a bogus second "command"
871
+ // from the leftover text failing with "'world' is not recognized..."). Passing the resolved
872
+ // absolute exe path with shell:false hands the args array to the OS process-create call
873
+ // directly, with zero shell metacharacter interpretation -- verified safe for arbitrary argv
874
+ // content (long strings containing `&`, quotes, newlines) via direct spawnSync reproduction.
875
+ const bunPath = bunR.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean)[0];
876
+ return { cmd: bunPath || 'bun', baseArgs: ['x', pkgSpec], shell: false };
865
877
  }
866
878
  const cachedBin = findCachedBunRunnerBin();
867
879
  if (cachedBin) return { cmd: process.execPath, baseArgs: [cachedBin], shell: false };
@@ -869,11 +881,16 @@ function findBrowserRunner() {
869
881
  if (r.status === 0 && r.stdout.trim()) {
870
882
  const candidates = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
871
883
  const cmd = candidates.find(c => c.toLowerCase().endsWith('.cmd')) || candidates.find(c => !c.toLowerCase().endsWith('.ps1')) || candidates[0];
872
- if (cmd) return { cmd, baseArgs: [], shell: process.platform === 'win32' };
884
+ // A resolved .exe candidate is a real binary and can run shell:false (same reasoning as the bun
885
+ // case above). Only a genuine .cmd/.bat wrapper needs shell:true on Windows -- cmd.exe is the
886
+ // only thing that can directly execute a .cmd file -- so scope the shell path to that case.
887
+ if (cmd) return { cmd, baseArgs: [], shell: process.platform === 'win32' && cmd.toLowerCase().endsWith('.cmd') };
873
888
  }
874
889
  const npxR = spawnSync(whichCmd, ['npx'], { encoding: 'utf-8', shell: true });
875
890
  if (npxR.status === 0 && npxR.stdout.trim()) {
876
- return { cmd: 'npx', baseArgs: ['-y', BROWSER_RUNNER_BIN], shell: true };
891
+ // npx resolves to npx.cmd on Windows, which genuinely requires shell:true to execute (see
892
+ // above). Non-Windows npx is a real executable/shebang script and needs no shell.
893
+ return { cmd: 'npx', baseArgs: ['-y', BROWSER_RUNNER_BIN], shell: process.platform === 'win32' };
877
894
  }
878
895
  return null;
879
896
  }
@@ -1164,11 +1181,30 @@ function playwriterHomeFor(cwd, claudeSessionId) {
1164
1181
  return path.join(cwd, '.gm', `pw-sock-${sessionProfileSlug(claudeSessionId)}`);
1165
1182
  }
1166
1183
 
1184
+ // cmd.exe (the shell Node's spawnSync{shell:true} uses on Windows for a .cmd/.bat target) treats
1185
+ // &|<>^ as command-line metacharacters EVEN INSIDE a double-quoted argument -- double-quoting alone
1186
+ // (the prior logic here) stops whitespace-splitting but does not stop cmd.exe from splitting
1187
+ // `foo&bar` into two separate commands. Real script/URL arguments passed through the browser verb
1188
+ // routinely contain `&` (e.g. `?a=1&b=2` query strings), which was being silently truncated
1189
+ // mid-argument on any shell:true path. Escaping each metacharacter with a caret (^) inside the
1190
+ // quoted string is the standard cmd.exe-safe encoding; this is the remaining defense for the
1191
+ // npx.cmd/.cmd-wrapper fallback paths that genuinely require shell:true (the bun path above no
1192
+ // longer needs this at all, since it now spawns the resolved .exe directly with shell:false).
1193
+ function cmdExeQuote(s) {
1194
+ const str = String(s);
1195
+ const escaped = str.replace(/"/g, '\\"').replace(/[&|<>^]/g, '^$&');
1196
+ return `"${escaped}"`;
1197
+ }
1198
+
1167
1199
  function runBrowserRunner(pw, args, timeoutMs, cwd, claudeSessionId) {
1168
1200
  const allArgs = [...pw.baseArgs, ...args];
1169
1201
  const useShell = !!pw.shell;
1170
1202
  const spawnCmd = useShell && /\s/.test(pw.cmd) ? `"${pw.cmd}"` : pw.cmd;
1171
- const spawnArgs = useShell ? allArgs.map(a => /[\s"]/.test(String(a)) ? `"${String(a).replace(/"/g, '\\"')}"` : a) : allArgs;
1203
+ const spawnArgs = useShell
1204
+ ? (process.platform === 'win32'
1205
+ ? allArgs.map(a => cmdExeQuote(a))
1206
+ : allArgs.map(a => /[\s"]/.test(String(a)) ? `"${String(a).replace(/"/g, '\\"')}"` : a))
1207
+ : allArgs;
1172
1208
  const env = { ...process.env };
1173
1209
  const sockDir = playwriterHomeFor(cwd, claudeSessionId);
1174
1210
  try { fs.mkdirSync(sockDir, { recursive: true }); } catch (_) {}
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1913",
3
+ "version": "2.0.1914",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1913",
3
+ "version": "2.0.1914",
4
4
  "description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",