gm-skill 2.0.1222 → 2.0.1224
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/README.md +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +137 -5
- package/gm.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ An earlier generation fanned out fifteen per-platform downstream repos (gm-cc, g
|
|
|
35
35
|
|
|
36
36
|
## Version
|
|
37
37
|
|
|
38
|
-
`2.0.
|
|
38
|
+
`2.0.1224` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
|
|
39
39
|
|
|
40
40
|
## Source of truth
|
|
41
41
|
|
|
@@ -651,9 +651,48 @@ function ensureGitignored(cwd, entry) {
|
|
|
651
651
|
} catch (_) {}
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
function isProcessAliveSync(pid) {
|
|
655
|
+
if (!pid || typeof pid !== 'number' || pid <= 0) return false;
|
|
656
|
+
try {
|
|
657
|
+
process.kill(pid, 0);
|
|
658
|
+
return true;
|
|
659
|
+
} catch (e) {
|
|
660
|
+
return e && e.code === 'EPERM';
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function readSingletonLockPid(profileDir) {
|
|
665
|
+
const lock = path.join(profileDir, 'SingletonLock');
|
|
666
|
+
try {
|
|
667
|
+
let target;
|
|
668
|
+
try {
|
|
669
|
+
target = fs.readlinkSync(lock);
|
|
670
|
+
} catch (_) {
|
|
671
|
+
try { target = fs.readFileSync(lock, 'utf-8'); } catch (__) { return null; }
|
|
672
|
+
}
|
|
673
|
+
if (!target) return null;
|
|
674
|
+
const m = String(target).match(/-(\d+)\s*$/);
|
|
675
|
+
if (m) return parseInt(m[1], 10);
|
|
676
|
+
const m2 = String(target).match(/(\d+)/);
|
|
677
|
+
if (m2) return parseInt(m2[1], 10);
|
|
678
|
+
} catch (_) {}
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
|
|
654
682
|
function isProfileLocked(profileDir) {
|
|
655
683
|
const lock = path.join(profileDir, 'SingletonLock');
|
|
656
|
-
|
|
684
|
+
if (!fs.existsSync(lock)) return false;
|
|
685
|
+
const holderPid = readSingletonLockPid(profileDir);
|
|
686
|
+
if (holderPid != null && !isProcessAliveSync(holderPid)) {
|
|
687
|
+
try { fs.unlinkSync(lock); } catch (_) {}
|
|
688
|
+
try { fs.unlinkSync(path.join(profileDir, 'SingletonCookie')); } catch (_) {}
|
|
689
|
+
try { fs.unlinkSync(path.join(profileDir, 'SingletonSocket')); } catch (_) {}
|
|
690
|
+
logEvent('bootstrap', 'browser-profile.lock-cleared', {
|
|
691
|
+
profileDir, dead_pid: holderPid,
|
|
692
|
+
});
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
return true;
|
|
657
696
|
}
|
|
658
697
|
|
|
659
698
|
function acquireProfileDir(cwd) {
|
|
@@ -669,6 +708,71 @@ function acquireProfileDir(cwd) {
|
|
|
669
708
|
return fallback;
|
|
670
709
|
}
|
|
671
710
|
|
|
711
|
+
function cleanDeadProfileFragments(cwd) {
|
|
712
|
+
try {
|
|
713
|
+
const gmDir = path.join(cwd, '.gm');
|
|
714
|
+
if (!fs.existsSync(gmDir)) return { cleaned: 0 };
|
|
715
|
+
let cleaned = 0;
|
|
716
|
+
for (const name of fs.readdirSync(gmDir)) {
|
|
717
|
+
const m = name.match(/^browser-profile-(\d+)$/);
|
|
718
|
+
if (!m) continue;
|
|
719
|
+
const pid = parseInt(m[1], 10);
|
|
720
|
+
if (!isProcessAliveSync(pid)) {
|
|
721
|
+
try {
|
|
722
|
+
fs.rmSync(path.join(gmDir, name), { recursive: true, force: true });
|
|
723
|
+
cleaned++;
|
|
724
|
+
} catch (_) {}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (cleaned > 0) {
|
|
728
|
+
logEvent('bootstrap', 'browser-profile.hygiene', { cwd, cleaned });
|
|
729
|
+
}
|
|
730
|
+
return { cleaned };
|
|
731
|
+
} catch (_) {
|
|
732
|
+
return { cleaned: 0 };
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function isPortReachableSync(host, port, timeoutMs) {
|
|
737
|
+
const r = spawnSync(process.execPath, ['-e', `
|
|
738
|
+
const net = require('net');
|
|
739
|
+
const s = net.connect({ port: ${port}, host: ${JSON.stringify(host)} });
|
|
740
|
+
let done = false;
|
|
741
|
+
s.on('connect', () => { done = true; s.destroy(); process.exit(0); });
|
|
742
|
+
s.on('error', () => { if (!done) process.exit(1); });
|
|
743
|
+
setTimeout(() => { if (!done) { s.destroy(); process.exit(1); } }, ${timeoutMs || 800});
|
|
744
|
+
`], { timeout: (timeoutMs || 800) + 2000 });
|
|
745
|
+
return r.status === 0;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
let _acptoapiBoot = { spawned_at: 0, pid: null };
|
|
749
|
+
function ensureAcptoapi() {
|
|
750
|
+
const host = '127.0.0.1';
|
|
751
|
+
const port = 4800;
|
|
752
|
+
try {
|
|
753
|
+
if (isPortReachableSync(host, port, 500)) {
|
|
754
|
+
_acptoapiBoot = { spawned_at: 0, pid: null, status: 'already-running' };
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (_acptoapiBoot.spawned_at && Date.now() - _acptoapiBoot.spawned_at < 30000) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const isWindows = process.platform === 'win32';
|
|
761
|
+
const cmd = isWindows ? 'bun.exe' : 'bun';
|
|
762
|
+
const child = spawn(cmd, ['x', 'acptoapi@latest'], {
|
|
763
|
+
detached: true,
|
|
764
|
+
stdio: 'ignore',
|
|
765
|
+
windowsHide: true,
|
|
766
|
+
shell: false,
|
|
767
|
+
});
|
|
768
|
+
child.unref();
|
|
769
|
+
_acptoapiBoot = { spawned_at: Date.now(), pid: child.pid, status: 'spawned' };
|
|
770
|
+
logEvent('bootstrap', 'acptoapi.spawned', { pid: child.pid, port });
|
|
771
|
+
} catch (e) {
|
|
772
|
+
logEvent('bootstrap', 'acptoapi.spawn-failed', { error: e && e.message });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
672
776
|
function findFreePortSync() {
|
|
673
777
|
const r = spawnSync(process.execPath, ['-e', `
|
|
674
778
|
const net = require('net');
|
|
@@ -722,10 +826,31 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
722
826
|
const ports = readJsonFile(portsFile, {});
|
|
723
827
|
const sessions = readJsonFile(sessionsFile, {});
|
|
724
828
|
const existing = ports[claudeSessionId];
|
|
725
|
-
if (existing && existing.port
|
|
726
|
-
const
|
|
727
|
-
|
|
829
|
+
if (existing && existing.port) {
|
|
830
|
+
const wantProfile = path.join(cwd, '.gm', 'browser-profile');
|
|
831
|
+
const pidOk = existing.pid && isProcessAliveSync(existing.pid);
|
|
832
|
+
const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(path.join(cwd, '.gm', 'browser-profile'));
|
|
833
|
+
const portOk = isPortAliveSync(existing.port);
|
|
834
|
+
if (pidOk && profileOk && portOk) {
|
|
835
|
+
const pwIds = sessions[claudeSessionId] || [];
|
|
836
|
+
if (pwIds.length > 0) return pwIds[0];
|
|
837
|
+
} else {
|
|
838
|
+
const reason = !pidOk ? 'pid-dead' : !profileOk ? 'profile-drift' : 'port-dead';
|
|
839
|
+
logEvent('hook', 'deviation.browser-profile-collision', {
|
|
840
|
+
sid: claudeSessionId,
|
|
841
|
+
stale_pid: existing.pid || null,
|
|
842
|
+
stale_port: existing.port || null,
|
|
843
|
+
stale_profile: existing.profileDir || null,
|
|
844
|
+
want_profile: wantProfile,
|
|
845
|
+
reason,
|
|
846
|
+
});
|
|
847
|
+
delete ports[claudeSessionId];
|
|
848
|
+
delete sessions[claudeSessionId];
|
|
849
|
+
try { writeJsonFile(portsFile, ports); } catch (_) {}
|
|
850
|
+
try { writeJsonFile(sessionsFile, sessions); } catch (_) {}
|
|
851
|
+
}
|
|
728
852
|
}
|
|
853
|
+
cleanDeadProfileFragments(cwd);
|
|
729
854
|
const chrome = findChrome();
|
|
730
855
|
if (!chrome) throw new Error('Chrome not found. Please install Google Chrome.');
|
|
731
856
|
const profileDir = acquireProfileDir(cwd);
|
|
@@ -1385,7 +1510,12 @@ function makeHostFunctions(instanceRef) {
|
|
|
1385
1510
|
});
|
|
1386
1511
|
}
|
|
1387
1512
|
const pwSessionId = getOrCreateBrowserSession(cwd, sessionId, pw);
|
|
1388
|
-
const
|
|
1513
|
+
const portsAfter = readJsonFile(browserPortsFile(cwd), {});
|
|
1514
|
+
const livePort = portsAfter[sessionId] && portsAfter[sessionId].port;
|
|
1515
|
+
const directArgs = (livePort && isPortAliveSync(livePort))
|
|
1516
|
+
? [`--direct=localhost:${livePort}`]
|
|
1517
|
+
: [];
|
|
1518
|
+
const r = runPlaywriter(pw, ['-s', pwSessionId, '--timeout', '14000', ...directArgs, '-e', body], 60000);
|
|
1389
1519
|
return writeWasmJson(instanceRef.value, {
|
|
1390
1520
|
ok: r.status === 0,
|
|
1391
1521
|
stdout: scrubBrowserRunnerText(r.stdout || ''),
|
|
@@ -1465,6 +1595,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1465
1595
|
fs.mkdirSync(outDir, { recursive: true });
|
|
1466
1596
|
|
|
1467
1597
|
try { ensureSpoolPollGate(process.env.CLAUDE_PROJECT_DIR || process.cwd()); } catch (_) {}
|
|
1598
|
+
try { ensureAcptoapi(); } catch (_) {}
|
|
1468
1599
|
|
|
1469
1600
|
const LOCK_PATH = path.join(spoolDir, '.watcher.lock');
|
|
1470
1601
|
let _ownWrapperSha12 = '';
|
|
@@ -1971,6 +2102,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1971
2102
|
}
|
|
1972
2103
|
setInterval(writeStatus, 5000);
|
|
1973
2104
|
writeStatus();
|
|
2105
|
+
setInterval(() => { try { ensureAcptoapi(); } catch (_) {} }, 60000);
|
|
1974
2106
|
|
|
1975
2107
|
const UPDATE_AVAILABLE_PATH = path.join(spoolDir, '.update-available.json');
|
|
1976
2108
|
const UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000;
|
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.1224",
|
|
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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"gm.json"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"gm-plugkit": "^2.0.
|
|
42
|
+
"gm-plugkit": "^2.0.1224"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=16.0.0"
|