gm-skill 2.0.1488 → 2.0.1490

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/AGENTS.md CHANGED
@@ -40,7 +40,7 @@ Agents dispatch verbs by writing to `.gm/exec-spool/in/<verb>/<N>.txt` (request
40
40
 
41
41
  **git verbs**: git is a first-class spool surface, never a shell command; `git_finalize {message}` is the bundled COMPLETE-phase push surface and `git_push` is the only admissible raw push (porcelain-gated, rebase-retry). A git-dominant `bash`/`powershell` body is gated (`deviation.bash-git-bypass`). Full per-verb shapes, host_git `.exe` resolution, and the gate detail live in rs-learn (`recall: git verbs rs-plugkit spool surface`).
42
42
 
43
- **filter verb**: pure stdout → compact-stdout transformation. Body `{kind, input, ...opts}` where kind is one of `grep`, `ls`, `tree`, `json`, `diff`, `git-status`, `log`. Returns `{output, stats:{bytes_in, bytes_out, saved_pct, ...}}`. Pipe raw command output through filter before letting it enter context, in-wasm, no subprocess. The bootstrap fetches only `plugkit.wasm`, there is no separate filter/rtk binary download.
43
+ **filter verb**: pure stdout → compact-stdout transformation, in-wasm, no subprocess; pipe raw command output through it before it enters context. Full per-kind spec in rs-learn (`recall: filter verb rs-plugkit spool spec`).
44
44
 
45
45
  ## Documentation Policy
46
46
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1488",
3
+ "version": "2.0.1490",
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": {
@@ -677,15 +677,24 @@ function isProfileLocked(profileDir) {
677
677
  return true;
678
678
  }
679
679
 
680
- function acquireProfileDir(cwd) {
680
+ function sessionProfileSlug(claudeSessionId) {
681
+ const s = String(claudeSessionId || 'default').replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 64);
682
+ return s || 'default';
683
+ }
684
+
685
+ function sessionProfileDir(cwd, claudeSessionId) {
686
+ return path.join(cwd, '.gm', `browser-profile-${sessionProfileSlug(claudeSessionId)}`);
687
+ }
688
+
689
+ function acquireProfileDir(cwd, claudeSessionId) {
681
690
  const gmDir = path.join(cwd, '.gm');
682
691
  try { fs.mkdirSync(gmDir, { recursive: true }); } catch (_) {}
683
- const primary = path.join(gmDir, 'browser-profile');
684
692
  ensureGitignored(cwd, '.gm/browser-profile/');
685
693
  ensureGitignored(cwd, '.gm/browser-profile-*/');
694
+ const primary = sessionProfileDir(cwd, claudeSessionId);
686
695
  try { fs.mkdirSync(primary, { recursive: true }); } catch (_) {}
687
696
  if (!isProfileLocked(primary)) return primary;
688
- const fallback = path.join(gmDir, `browser-profile-${process.pid}`);
697
+ const fallback = path.join(gmDir, `browser-profile-${sessionProfileSlug(claudeSessionId)}-${process.pid}`);
689
698
  try { fs.mkdirSync(fallback, { recursive: true }); } catch (_) {}
690
699
  return fallback;
691
700
  }
@@ -696,15 +705,21 @@ function cleanDeadProfileFragments(cwd) {
696
705
  if (!fs.existsSync(gmDir)) return { cleaned: 0 };
697
706
  let cleaned = 0;
698
707
  for (const name of fs.readdirSync(gmDir)) {
699
- const m = name.match(/^browser-profile-(\d+)$/);
700
- if (!m) continue;
701
- const pid = parseInt(m[1], 10);
702
- if (!isProcessAliveSync(pid)) {
703
- try {
704
- fs.rmSync(path.join(gmDir, name), { recursive: true, force: true });
705
- cleaned++;
706
- } catch (_) {}
708
+ if (!/^browser-profile($|-)/.test(name)) continue;
709
+ const dir = path.join(gmDir, name);
710
+ const pidM = name.match(/-(\d+)$/);
711
+ if (pidM) {
712
+ if (!isProcessAliveSync(parseInt(pidM[1], 10))) {
713
+ try { fs.rmSync(dir, { recursive: true, force: true }); cleaned++; } catch (_) {}
714
+ }
715
+ continue;
707
716
  }
717
+ try {
718
+ if (fs.existsSync(dir) && !isProfileLocked(dir)) {
719
+ fs.rmSync(dir, { recursive: true, force: true });
720
+ cleaned++;
721
+ }
722
+ } catch (_) {}
708
723
  }
709
724
  if (cleaned > 0) {
710
725
  logEvent('bootstrap', 'browser-profile.hygiene', { cwd, cleaned });
@@ -982,9 +997,9 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
982
997
  const sessions = readJsonFile(sessionsFile, {});
983
998
  const existing = ports[claudeSessionId];
984
999
  if (existing && existing.pid && existing.wsEndpoint) {
985
- const wantProfile = path.join(cwd, '.gm', 'browser-profile');
1000
+ const wantProfile = sessionProfileDir(cwd, claudeSessionId);
986
1001
  const pidOk = isProcessAliveSync(existing.pid);
987
- const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(path.join(cwd, '.gm', 'browser-profile'));
1002
+ const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(wantProfile);
988
1003
  const cdpOk = pidOk && !!fetchJsonSync(`http://127.0.0.1:${existing.port}/json/version`, 1000);
989
1004
  if (pidOk && profileOk && cdpOk) {
990
1005
  const pwIds = sessions[claudeSessionId] || [];
@@ -1033,7 +1048,7 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
1033
1048
  }
1034
1049
  }
1035
1050
  cleanDeadProfileFragments(cwd);
1036
- const profileDir = acquireProfileDir(cwd);
1051
+ const profileDir = acquireProfileDir(cwd, claudeSessionId);
1037
1052
  const aliveCdpForProfile = (() => {
1038
1053
  for (const key of Object.keys(ports)) {
1039
1054
  const ent = ports[key];
@@ -144,13 +144,16 @@ function spawnWatcher(bootReason) {
144
144
  const shutdownReason = readShutdownReason();
145
145
  const reason = shutdownReason && shutdownReason.reason;
146
146
  const idleClean = reason === 'idle';
147
+ const lockRejected = code === 75;
147
148
  const plannedReasons = new Set(['idle', 'sigterm', 'version-change', 'wrapper-change', 'peer-stale-takeover', 'external-planned']);
148
- const isPlanned = plannedReasons.has(reason);
149
+ const isPlanned = plannedReasons.has(reason) || lockRejected;
149
150
  const eventName = idleClean
150
151
  ? 'supervisor.watcher-exited-idle'
151
152
  : reason === 'version-change'
152
153
  ? 'supervisor.watcher-exited-for-update'
153
- : 'supervisor.watcher-exited-unexpectedly';
154
+ : lockRejected
155
+ ? 'supervisor.watcher-exited-lock-rejected'
156
+ : 'supervisor.watcher-exited-unexpectedly';
154
157
  logEvent(eventName, {
155
158
  watcher_pid: currentChildPid,
156
159
  exit_code: code,
@@ -166,6 +169,11 @@ function spawnWatcher(bootReason) {
166
169
  try { fs.unlinkSync(SUPERVISOR_PATH); } catch (_) {}
167
170
  process.exit(0);
168
171
  }
172
+ if (lockRejected) {
173
+ writeSupervisorStatus('exited-lock-rejected', { watcher_pid: currentChildPid });
174
+ try { fs.unlinkSync(SUPERVISOR_PATH); } catch (_) {}
175
+ process.exit(0);
176
+ }
169
177
  const respawnReason = reason === 'version-change' ? 'planned-restart-version-change' : 'unplanned-restart-after-exit';
170
178
  writeSupervisorStatus('restarting', {
171
179
  prior_watcher_pid: currentChildPid,
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1488",
3
+ "version": "2.0.1490",
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.1488",
3
+ "version": "2.0.1490",
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",