gm-skill 2.0.1912 → 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.
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +75 -4
- package/gm.json +1 -1
- package/package.json +1 -1
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
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": {
|
|
@@ -758,6 +758,29 @@ function findCachedBunRunnerBin() {
|
|
|
758
758
|
return null;
|
|
759
759
|
}
|
|
760
760
|
|
|
761
|
+
// Cache dir entries are named `<pkg>@<version>[@@@N]` (the `@@@N` suffix is bun's own
|
|
762
|
+
// disambiguator for multiple cache slots of the same resolved version, not part of the semver).
|
|
763
|
+
// Picks the highest semver-looking version present so a stale older cache entry left over from
|
|
764
|
+
// a prior gm-plugkit run doesn't win over a newer one that's also cached.
|
|
765
|
+
function findCachedBunRunnerVersion() {
|
|
766
|
+
try {
|
|
767
|
+
const cacheDir = path.join(os.homedir(), '.bun', 'install', 'cache');
|
|
768
|
+
const entries = fs.readdirSync(cacheDir).filter(n => n.startsWith(`${BROWSER_RUNNER_BIN}@`));
|
|
769
|
+
const versions = entries
|
|
770
|
+
.map(n => n.slice(BROWSER_RUNNER_BIN.length + 1).split('@@@')[0])
|
|
771
|
+
.filter(v => /^\d+\.\d+\.\d+/.test(v));
|
|
772
|
+
if (!versions.length) return null;
|
|
773
|
+
versions.sort((a, b) => {
|
|
774
|
+
const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
|
|
775
|
+
for (let i = 0; i < 3; i++) { if (pa[i] !== pb[i]) return pb[i] - pa[i]; }
|
|
776
|
+
return 0;
|
|
777
|
+
});
|
|
778
|
+
return versions[0];
|
|
779
|
+
} catch (_) {
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
761
784
|
// playwriter's CLI `--timeout` option is parsed as a raw string (its own arg parser does not
|
|
762
785
|
// coerce it despite the zod schema declaring z.number()), and that string is forwarded verbatim
|
|
763
786
|
// into `vm.runInContext(..., { timeout })`, which throws
|
|
@@ -826,7 +849,31 @@ function findBrowserRunner() {
|
|
|
826
849
|
const whichCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
827
850
|
const bunR = spawnSync(whichCmd, ['bun'], { encoding: 'utf-8', shell: true });
|
|
828
851
|
if (bunR.status === 0 && bunR.stdout.trim()) {
|
|
829
|
-
|
|
852
|
+
// `bun x <pkg>@latest` re-resolves the `@latest` tag against the npm registry on every
|
|
853
|
+
// single invocation, even when a resolved copy already sits in bun's own content-addressed
|
|
854
|
+
// cache (~/.bun/install/cache/<pkg>@<version>) from a prior run -- a redundant network
|
|
855
|
+
// round-trip per browser-verb dispatch. Observed on Windows to occasionally never return
|
|
856
|
+
// (hangs past "Resolved, downloaded and extracted" with no further output or error), which
|
|
857
|
+
// wedges every subsequent browser dispatch behind a 30s+ spawnSync timeout for no benefit,
|
|
858
|
+
// since the pinned exact version was already available locally. Prefer `bun x <pkg>@<exact
|
|
859
|
+
// cached version>` when a cached copy exists -- this still goes through `bun x`'s real
|
|
860
|
+
// dependency-tree resolution (the reason `bun x` is preferred over the raw cached bin.js
|
|
861
|
+
// above), it just skips the registry round-trip for the tag lookup itself.
|
|
862
|
+
const cachedVersion = findCachedBunRunnerVersion();
|
|
863
|
+
const pkgSpec = cachedVersion ? `${BROWSER_RUNNER_BIN}@${cachedVersion}` : `${BROWSER_RUNNER_BIN}@latest`;
|
|
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 };
|
|
830
877
|
}
|
|
831
878
|
const cachedBin = findCachedBunRunnerBin();
|
|
832
879
|
if (cachedBin) return { cmd: process.execPath, baseArgs: [cachedBin], shell: false };
|
|
@@ -834,11 +881,16 @@ function findBrowserRunner() {
|
|
|
834
881
|
if (r.status === 0 && r.stdout.trim()) {
|
|
835
882
|
const candidates = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
836
883
|
const cmd = candidates.find(c => c.toLowerCase().endsWith('.cmd')) || candidates.find(c => !c.toLowerCase().endsWith('.ps1')) || candidates[0];
|
|
837
|
-
|
|
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') };
|
|
838
888
|
}
|
|
839
889
|
const npxR = spawnSync(whichCmd, ['npx'], { encoding: 'utf-8', shell: true });
|
|
840
890
|
if (npxR.status === 0 && npxR.stdout.trim()) {
|
|
841
|
-
|
|
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' };
|
|
842
894
|
}
|
|
843
895
|
return null;
|
|
844
896
|
}
|
|
@@ -1129,11 +1181,30 @@ function playwriterHomeFor(cwd, claudeSessionId) {
|
|
|
1129
1181
|
return path.join(cwd, '.gm', `pw-sock-${sessionProfileSlug(claudeSessionId)}`);
|
|
1130
1182
|
}
|
|
1131
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
|
+
|
|
1132
1199
|
function runBrowserRunner(pw, args, timeoutMs, cwd, claudeSessionId) {
|
|
1133
1200
|
const allArgs = [...pw.baseArgs, ...args];
|
|
1134
1201
|
const useShell = !!pw.shell;
|
|
1135
1202
|
const spawnCmd = useShell && /\s/.test(pw.cmd) ? `"${pw.cmd}"` : pw.cmd;
|
|
1136
|
-
const spawnArgs = useShell
|
|
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;
|
|
1137
1208
|
const env = { ...process.env };
|
|
1138
1209
|
const sockDir = playwriterHomeFor(cwd, claudeSessionId);
|
|
1139
1210
|
try { fs.mkdirSync(sockDir, { recursive: true }); } catch (_) {}
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
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",
|