gm-skill 2.0.1990 → 2.0.1992

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 +1 @@
1
- 5059b361806556c8e7193cdaf65258917e1916056d063bbe1a705ee2ac8da610 plugkit-slim.wasm
1
+ 0d2c2d36dfd496b0eea0998b38b86d9496eb660757a6e91ddae6b49dc93ef878 plugkit-slim.wasm
@@ -1 +1 @@
1
- 0.1.915
1
+ 0.1.916
@@ -1 +1 @@
1
- eacc4aa56b71be544adb0c370920bc7730e7d61b887df3563cef8a3ba6cb9281 plugkit.wasm
1
+ fe337c1fdb928a1f15774b3fa0371b4b51e6c3fdbf77f2131da1529610a590c6 plugkit.wasm
package/gm-plugkit/cli.js CHANGED
@@ -6,24 +6,12 @@ const os = require('os');
6
6
  const path = require('path');
7
7
  const cp = require('child_process');
8
8
  const { ensureReady, startSpoolDaemon, gmToolsDir, readVersionFile, ensureGmPlugkitVersionFresh, ensureSkillMdFresh, ensureWrapperFresh, isReady, getWasmPath, readPinnedGmPlugkitVersion, spawnPinnedBoot, resolveProjectRoot } = require('./bootstrap');
9
+ const { pidAliveSync, waitForPidDeath } = require('./gm-process');
9
10
 
10
11
  function getWasmPathSafe() {
11
12
  try { return getWasmPath(); } catch (_) { return null; }
12
13
  }
13
14
 
14
- function pidAliveSync(pid) {
15
- try { process.kill(pid, 0); return true; } catch (_) { return false; }
16
- }
17
-
18
- function waitForPidDeath(pid, timeoutMs) {
19
- const deadline = Date.now() + timeoutMs;
20
- while (Date.now() < deadline) {
21
- if (!pidAliveSync(pid)) return true;
22
- try { cp.execFileSync(process.platform === 'win32' ? 'ping' : 'sleep', process.platform === 'win32' ? ['-n', '2', '127.0.0.1'] : ['0.3'], { stdio: 'ignore', windowsHide: true }); } catch (_) {}
23
- }
24
- return !pidAliveSync(pid);
25
- }
26
-
27
15
  function spawnBackgroundFreshnessCheck(reason) {
28
16
  try {
29
17
  const child = cp.spawn(process.execPath, [__filename.replace(/cli\.js$/, 'bootstrap.js')], {
@@ -13,4 +13,22 @@ function pidCommandLineForKillGuard(pid) {
13
13
  } catch (_) { return ''; }
14
14
  }
15
15
 
16
- module.exports = { pidCommandLineForKillGuard };
16
+ // Is `pid` still alive? signal-0 probe (throws ESRCH once the process is gone).
17
+ function pidAliveSync(pid) {
18
+ try { process.kill(pid, 0); return true; } catch (_) { return false; }
19
+ }
20
+
21
+ // Block (via a short spawnSync sleep, no async) until `pid` dies or timeoutMs
22
+ // elapses. Shared by cli.js (daemon recycle) and supervisor.js (killChild) —
23
+ // both previously carried a byte-divergent inline copy (execFileSync vs
24
+ // spawnSync) of this exact loop.
25
+ function waitForPidDeath(pid, timeoutMs) {
26
+ const deadline = Date.now() + timeoutMs;
27
+ while (Date.now() < deadline) {
28
+ if (!pidAliveSync(pid)) return true;
29
+ try { spawnSync(process.platform === 'win32' ? 'ping' : 'sleep', process.platform === 'win32' ? ['-n', '2', '127.0.0.1'] : ['0.3'], { stdio: 'ignore', windowsHide: true }); } catch (_) {}
30
+ }
31
+ return !pidAliveSync(pid);
32
+ }
33
+
34
+ module.exports = { pidCommandLineForKillGuard, pidAliveSync, waitForPidDeath };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1990",
3
+ "version": "2.0.1992",
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": {
@@ -1 +1 @@
1
- 5059b361806556c8e7193cdaf65258917e1916056d063bbe1a705ee2ac8da610 plugkit-slim.wasm
1
+ 0d2c2d36dfd496b0eea0998b38b86d9496eb660757a6e91ddae6b49dc93ef878 plugkit-slim.wasm
@@ -1 +1 @@
1
- 0.1.915
1
+ 0.1.916
@@ -8,7 +8,7 @@ const crypto = require('crypto');
8
8
  const { spawn, spawnSync } = require('child_process');
9
9
  const { gmToolsDir, resolveProjectRoot } = require('./bootstrap');
10
10
  const { logEvent: _sharedLogEvent, GM_LOG_ROOT: _sharedGmLogRoot } = require('./gm-log');
11
- const { pidCommandLineForKillGuard: _sharedPidCommandLine } = require('./gm-process');
11
+ const { pidCommandLineForKillGuard: _sharedPidCommandLine, pidAliveSync, waitForPidDeath } = require('./gm-process');
12
12
 
13
13
  function wrapperSha12OnDisk() {
14
14
  try {
@@ -149,19 +149,6 @@ function readShutdownReason() {
149
149
  // deliberate upgrade recycle as a SILENT ABORT / unplanned-restart critical. Write
150
150
  // the planned reason on the child's behalf BEFORE killing, so the next boot reads a
151
151
  // fresh planned shutdown reason and classifies the restart correctly.
152
- function pidAliveSync(pid) {
153
- try { process.kill(pid, 0); return true; } catch (_) { return false; }
154
- }
155
-
156
- function waitForPidDeath(pid, timeoutMs) {
157
- const deadline = Date.now() + timeoutMs;
158
- while (Date.now() < deadline) {
159
- if (!pidAliveSync(pid)) return true;
160
- try { spawnSync(process.platform === 'win32' ? 'ping' : 'sleep', process.platform === 'win32' ? ['-n', '2', '127.0.0.1'] : ['0.3'], { stdio: 'ignore', windowsHide: true }); } catch (_) {}
161
- }
162
- return !pidAliveSync(pid);
163
- }
164
-
165
152
  function killChild(reason) {
166
153
  if (!currentChildPid) return;
167
154
  const pid = currentChildPid;
@@ -0,0 +1,26 @@
1
+ import fs from 'fs';
2
+
3
+ function atomicWriteRaw(filePath, data) {
4
+ const tmp = filePath + '.tmp.' + process.pid + '.' + Date.now() + '.' + Math.random().toString(36).slice(2, 8);
5
+ fs.writeFileSync(tmp, data);
6
+ try {
7
+ fs.renameSync(tmp, filePath);
8
+ } catch (err) {
9
+ try { fs.unlinkSync(tmp); } catch (_) {}
10
+ throw err;
11
+ }
12
+ }
13
+
14
+ function atomicWriteJson(filePath, obj) {
15
+ atomicWriteRaw(filePath, JSON.stringify(obj, null, 2));
16
+ }
17
+
18
+ function readJsonFile(fp, fallback) {
19
+ try { return JSON.parse(fs.readFileSync(fp, 'utf-8')); } catch (_) { return fallback; }
20
+ }
21
+
22
+ function writeJsonFile(fp, value) {
23
+ try { atomicWriteJson(fp, value); } catch (_) {}
24
+ }
25
+
26
+ export { atomicWriteRaw, atomicWriteJson, readJsonFile, writeJsonFile };
@@ -0,0 +1,81 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ function safeName(s) { return String(s).replace(/[^A-Za-z0-9._-]/g, '_'); }
5
+
6
+ function projectKvDir(ns) {
7
+ const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
8
+ return path.join(projectRoot, '.gm', 'disciplines', safeName(ns));
9
+ }
10
+
11
+ function makeLegacyKvDir(kvDir) {
12
+ return function legacyKvDir(ns) {
13
+ return path.join(kvDir, safeName(ns));
14
+ };
15
+ }
16
+
17
+ function makeKvFilePath() {
18
+ return function kvFilePath(ns, key, ensureDir) {
19
+ const dir = projectKvDir(ns);
20
+ if (ensureDir) fs.mkdirSync(dir, { recursive: true });
21
+ return path.join(dir, safeName(key) + '.json');
22
+ };
23
+ }
24
+
25
+ function makeKvReadResolve(legacyKvDir) {
26
+ const kvFilePath = makeKvFilePath();
27
+ return function kvReadResolve(ns, key) {
28
+ const fp = kvFilePath(ns, key);
29
+ if (fs.existsSync(fp)) return fp;
30
+ const legacy = path.join(legacyKvDir(ns), safeName(key) + '.json');
31
+ if (fs.existsSync(legacy)) return legacy;
32
+ return null;
33
+ };
34
+ }
35
+
36
+ function makeKvNamespaceDirs(legacyKvDir) {
37
+ return function kvNamespaceDirs(ns) {
38
+ const out = [];
39
+ const proj = projectKvDir(ns);
40
+ if (fs.existsSync(proj)) out.push(proj);
41
+ const legacy = legacyKvDir(ns);
42
+ if (fs.existsSync(legacy)) out.push(legacy);
43
+ return out;
44
+ };
45
+ }
46
+
47
+ function enabledDisciplineNamespaces(baseNs) {
48
+ const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
49
+ const set = new Set([baseNs]);
50
+ try {
51
+ const enabledPath = path.join(projectRoot, '.gm', 'disciplines', 'enabled.txt');
52
+ if (fs.existsSync(enabledPath)) {
53
+ const lines = fs.readFileSync(enabledPath, 'utf-8').split(/\r?\n/);
54
+ for (const ln of lines) {
55
+ const name = ln.trim();
56
+ if (name && !name.startsWith('#')) set.add(name);
57
+ }
58
+ }
59
+ } catch (_) {}
60
+ return Array.from(set);
61
+ }
62
+
63
+ function jaccardOverlap(a, b) {
64
+ if (!a || !b) return 0;
65
+ const tokenize = (s) => new Set(String(s).toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length >= 3));
66
+ const A = tokenize(a), B = tokenize(b);
67
+ if (A.size === 0 || B.size === 0) return 0;
68
+ let inter = 0;
69
+ for (const t of A) if (B.has(t)) inter++;
70
+ return inter / (A.size + B.size - inter);
71
+ }
72
+
73
+ function makeKvHelpers(kvDir) {
74
+ const legacyKvDir = makeLegacyKvDir(kvDir);
75
+ const kvFilePath = makeKvFilePath();
76
+ const kvReadResolve = makeKvReadResolve(legacyKvDir);
77
+ const kvNamespaceDirs = makeKvNamespaceDirs(legacyKvDir);
78
+ return { safeName, projectKvDir, legacyKvDir, kvFilePath, kvReadResolve, kvNamespaceDirs, enabledDisciplineNamespaces, jaccardOverlap };
79
+ }
80
+
81
+ export { safeName, projectKvDir, enabledDisciplineNamespaces, jaccardOverlap, makeKvHelpers };
@@ -0,0 +1,272 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ function makeTaskManager({ spawn, spawnSync, logEvent }) {
5
+ const __tasks = new Map();
6
+
7
+ function tasksDir(cwd) {
8
+ const d = path.join(cwd || process.cwd(), '.gm', 'exec-spool', 'tasks');
9
+ try { fs.mkdirSync(d, { recursive: true }); } catch (_) {}
10
+ return d;
11
+ }
12
+
13
+ function taskMetaPath(cwd, id) { return path.join(tasksDir(cwd), `${id}.json`); }
14
+ function taskOutPath(cwd, id, which) { return path.join(tasksDir(cwd), `${id}.${which}.log`); }
15
+
16
+ function writeTaskMeta(cwd, id, meta) {
17
+ try { fs.writeFileSync(taskMetaPath(cwd, id), JSON.stringify(meta, null, 2)); } catch (_) {}
18
+ }
19
+
20
+ function nextTaskId(cwd) {
21
+ const counterPath = path.join(tasksDir(cwd), '.counter');
22
+ let n = 0;
23
+ try { n = parseInt(fs.readFileSync(counterPath, 'utf-8'), 10) || 0; } catch (_) {}
24
+ n += 1;
25
+ try { fs.writeFileSync(counterPath, String(n)); } catch (_) {}
26
+ return `t${n}`;
27
+ }
28
+
29
+ let _jsRuntimeCmd = null;
30
+ function resolveJsRuntimeCmd() {
31
+ if (_jsRuntimeCmd) return _jsRuntimeCmd;
32
+ if (!/(^|[\\/])node(\.exe)?$/i.test(String(process.execPath || ''))) {
33
+ _jsRuntimeCmd = process.execPath;
34
+ return _jsRuntimeCmd;
35
+ }
36
+ try {
37
+ const which = process.platform === 'win32' ? 'where' : 'which';
38
+ const out = spawnSync(which, ['bun'], { encoding: 'utf-8', windowsHide: true });
39
+ const first = (out && out.stdout || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
40
+ if (first) { _jsRuntimeCmd = first; return _jsRuntimeCmd; }
41
+ } catch (_) {}
42
+ _jsRuntimeCmd = process.execPath;
43
+ return _jsRuntimeCmd;
44
+ }
45
+
46
+ function langToCmd(lang, code) {
47
+ if (lang === 'nodejs' || lang === 'js' || lang === 'javascript' || lang === 'node') return { cmd: resolveJsRuntimeCmd(), args: ['-e', code], stdinCode: null };
48
+ if (lang === 'python' || lang === 'py') return { cmd: 'python', args: ['-c', code], stdinCode: null };
49
+ if (lang === 'bash' || lang === 'sh' || lang === 'shell' || lang === 'zsh') return { cmd: 'bash', args: ['-c', code], stdinCode: null };
50
+ if (lang === 'powershell' || lang === 'ps1') return { cmd: 'powershell', args: ['-NoProfile', '-NonInteractive', '-Command', code], stdinCode: null };
51
+ if (lang === 'deno') return { cmd: 'deno', args: ['eval', code], stdinCode: null };
52
+ return null;
53
+ }
54
+
55
+ const TASK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
56
+
57
+ function spawnTask({ cwd, lang, code, timeoutMs }) {
58
+ const id = nextTaskId(cwd);
59
+ const built = langToCmd(lang, code);
60
+ if (!built) return { ok: false, error: `unsupported lang: ${lang}` };
61
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > TASK_MAX_TIMEOUT_MS) {
62
+ timeoutMs = TASK_MAX_TIMEOUT_MS;
63
+ }
64
+ const outLog = taskOutPath(cwd, id, 'stdout');
65
+ const errLog = taskOutPath(cwd, id, 'stderr');
66
+ let outFd = null, errFd = null;
67
+ try { outFd = fs.openSync(outLog, 'a'); } catch (_) {}
68
+ try { errFd = fs.openSync(errLog, 'a'); } catch (_) {}
69
+ const startedMs = Date.now();
70
+ const isPosix = process.platform !== 'win32';
71
+ const child = spawn(built.cmd, built.args, {
72
+ cwd: cwd || process.cwd(),
73
+ detached: isPosix,
74
+ stdio: ['ignore', outFd || 'ignore', errFd || 'ignore'],
75
+ windowsHide: true,
76
+ env: process.env,
77
+ });
78
+ try { if (outFd !== null) fs.closeSync(outFd); } catch (_) {}
79
+ try { if (errFd !== null) fs.closeSync(errFd); } catch (_) {}
80
+ const meta = {
81
+ id,
82
+ pid: child.pid,
83
+ pgid: isPosix ? child.pid : null,
84
+ lang,
85
+ cmd: built.cmd,
86
+ cwd: cwd || process.cwd(),
87
+ started_ms: startedMs,
88
+ timeout_ms: timeoutMs,
89
+ deadline_ms: startedMs + timeoutMs,
90
+ status: 'running',
91
+ exit_code: null,
92
+ stdout_log: outLog,
93
+ stderr_log: errLog,
94
+ };
95
+ __tasks.set(id, { child, meta });
96
+ writeTaskMeta(cwd, id, meta);
97
+ child.on('exit', (code, signal) => {
98
+ meta.status = signal ? 'killed' : (code === 0 ? 'completed' : 'failed');
99
+ meta.exit_code = code;
100
+ meta.signal = signal;
101
+ meta.ended_ms = Date.now();
102
+ writeTaskMeta(meta.cwd, id, meta);
103
+ });
104
+ child.on('error', (err) => {
105
+ meta.status = 'error';
106
+ meta.error = err.message;
107
+ meta.ended_ms = Date.now();
108
+ writeTaskMeta(meta.cwd, id, meta);
109
+ });
110
+ logEvent('plugkit', 'task.spawn', { task_id: id, pid: child.pid, lang, timeout_ms: timeoutMs });
111
+ return { ok: true, task_id: id, pid: child.pid, started_ms: startedMs };
112
+ }
113
+
114
+ function stopTaskById(id) {
115
+ const entry = __tasks.get(id);
116
+ if (!entry) {
117
+ return { ok: false, error: 'unknown task_id', task_id: id };
118
+ }
119
+ const { child, meta } = entry;
120
+ if (meta.status !== 'running') return { ok: true, already: meta.status, task_id: id };
121
+ const pid = meta.pid;
122
+ const isPosix = process.platform !== 'win32';
123
+ try {
124
+ if (isPosix && meta.pgid) {
125
+ try { process.kill(-meta.pgid, 'SIGTERM'); } catch (_) {}
126
+ } else {
127
+ try { child.kill('SIGTERM'); } catch (_) {}
128
+ }
129
+ } catch (_) {}
130
+ const graceTimer = setTimeout(() => {
131
+ if (meta.status !== 'running') return;
132
+ if (isPosix && meta.pgid) {
133
+ try { process.kill(-meta.pgid, 'SIGKILL'); } catch (_) {}
134
+ } else if (process.platform === 'win32') {
135
+ try { spawnSync('taskkill', ['/F', '/T', '/PID', String(pid)], { stdio: 'ignore', timeout: 3000 }); } catch (_) {}
136
+ } else {
137
+ try { child.kill('SIGKILL'); } catch (_) {}
138
+ }
139
+ }, 2000);
140
+ graceTimer.unref && graceTimer.unref();
141
+ logEvent('plugkit', 'task.stop', { task_id: id, pid });
142
+ return { ok: true, task_id: id, pid };
143
+ }
144
+
145
+ function tailFile(filePath, maxBytes) {
146
+ try {
147
+ const stat = fs.statSync(filePath);
148
+ if (stat.size <= maxBytes) return fs.readFileSync(filePath, 'utf-8');
149
+ const fd = fs.openSync(filePath, 'r');
150
+ try {
151
+ const buf = Buffer.alloc(maxBytes);
152
+ fs.readSync(fd, buf, 0, maxBytes, stat.size - maxBytes);
153
+ return buf.toString('utf-8');
154
+ } finally { try { fs.closeSync(fd); } catch (_) {} }
155
+ } catch (_) { return ''; }
156
+ }
157
+
158
+ function listTasks(cwd) {
159
+ const d = tasksDir(cwd);
160
+ const out = [];
161
+ try {
162
+ for (const entry of fs.readdirSync(d)) {
163
+ if (!entry.endsWith('.json') || entry.startsWith('.')) continue;
164
+ try {
165
+ const meta = JSON.parse(fs.readFileSync(path.join(d, entry), 'utf-8'));
166
+ out.push(meta);
167
+ } catch (_) {}
168
+ }
169
+ } catch (_) {}
170
+ return out;
171
+ }
172
+
173
+ function reapTimedOutTasks() {
174
+ const now = Date.now();
175
+ for (const [id, entry] of __tasks) {
176
+ const m = entry.meta;
177
+ if (m.status === 'running' && m.deadline_ms && now > m.deadline_ms) {
178
+ logEvent('plugkit', 'task.timeout', { task_id: id, pid: m.pid, deadline_ms: m.deadline_ms, now_ms: now });
179
+ stopTaskById(id);
180
+ }
181
+ }
182
+ }
183
+
184
+ function killAllTasks(reason) {
185
+ let killed = 0;
186
+ for (const [id, entry] of __tasks) {
187
+ if (entry.meta.status === 'running') {
188
+ stopTaskById(id);
189
+ killed += 1;
190
+ }
191
+ }
192
+ if (killed > 0) logEvent('plugkit', 'task.killAll', { reason, count: killed });
193
+ return killed;
194
+ }
195
+
196
+ function pidAliveLocal(pid) {
197
+ if (!Number.isFinite(pid) || pid <= 0) return false;
198
+ try { process.kill(pid, 0); return true; } catch (_) { return false; }
199
+ }
200
+
201
+ function sweepOrphanedTaskMetaOnBoot(cwd) {
202
+ let swept = 0;
203
+ try {
204
+ const dir = tasksDir(cwd);
205
+ const now = Date.now();
206
+ for (const name of fs.readdirSync(dir)) {
207
+ if (!/^t\d+\.json$/.test(name)) continue;
208
+ const metaPath = path.join(dir, name);
209
+ let meta = null;
210
+ try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8')); } catch (_) { continue; }
211
+ if (!meta || meta.status !== 'running') continue;
212
+ const stale = !pidAliveLocal(meta.pid) || (meta.deadline_ms && now > meta.deadline_ms);
213
+ if (!stale) continue;
214
+ if (pidAliveLocal(meta.pid)) {
215
+ try {
216
+ if (process.platform === 'win32') {
217
+ spawnSync('taskkill', ['/F', '/T', '/PID', String(meta.pid)], { stdio: 'ignore', windowsHide: true, timeout: 3000 });
218
+ } else {
219
+ try { process.kill(-meta.pid, 'SIGKILL'); } catch (_) { try { process.kill(meta.pid, 'SIGKILL'); } catch (_) {} }
220
+ }
221
+ } catch (_) {}
222
+ }
223
+ meta.status = 'reaped-on-boot';
224
+ meta.ended_ms = now;
225
+ try { fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2)); } catch (_) {}
226
+ swept += 1;
227
+ }
228
+ } catch (_) {}
229
+ if (swept > 0) logEvent('plugkit', 'task.bootSweepReaped', { cwd: cwd || process.cwd(), count: swept });
230
+ return swept;
231
+ }
232
+
233
+ function hostTaskProc(action, params) {
234
+ switch (action) {
235
+ case 'spawn': return spawnTask(params);
236
+ case 'stop': return stopTaskById(params.id || params.task_id);
237
+ case 'list': return { ok: true, tasks: listTasks(params.cwd) };
238
+ case 'output': return {
239
+ ok: true,
240
+ task_id: params.id || params.task_id,
241
+ stdout: tailFile(taskOutPath(params.cwd, params.id || params.task_id, 'stdout'), params.max_bytes || 65536),
242
+ stderr: tailFile(taskOutPath(params.cwd, params.id || params.task_id, 'stderr'), params.max_bytes || 65536),
243
+ };
244
+ case 'reap': { reapTimedOutTasks(); return { ok: true }; }
245
+ case 'killAll': { const n = killAllTasks(params.reason || 'host_task_proc'); return { ok: true, killed: n }; }
246
+ default: return { ok: false, error: `unknown action: ${action}` };
247
+ }
248
+ }
249
+
250
+ return {
251
+ __tasks,
252
+ tasksDir,
253
+ taskMetaPath,
254
+ taskOutPath,
255
+ writeTaskMeta,
256
+ nextTaskId,
257
+ resolveJsRuntimeCmd,
258
+ langToCmd,
259
+ TASK_MAX_TIMEOUT_MS,
260
+ spawnTask,
261
+ stopTaskById,
262
+ tailFile,
263
+ listTasks,
264
+ reapTimedOutTasks,
265
+ killAllTasks,
266
+ pidAliveLocal,
267
+ sweepOrphanedTaskMetaOnBoot,
268
+ hostTaskProc,
269
+ };
270
+ }
271
+
272
+ export { makeTaskManager };
@@ -0,0 +1,377 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+
5
+ function wasiFilesystemRootFor(gmToolsRoot) {
6
+ const projectSlug = crypto.createHash('sha256')
7
+ .update(String(process.env.CLAUDE_PROJECT_DIR || process.cwd()).toLowerCase().replace(/\\/g, '/'))
8
+ .digest('hex').slice(0, 16);
9
+ return path.join(gmToolsRoot, 'wasi-fs', projectSlug);
10
+ }
11
+
12
+ function makeWasiResolvePath(wasiFilesystemRoot) {
13
+ return function wasiResolvePath(relPath) {
14
+ const rel = String(relPath || '').replace(/\\/g, '/').replace(/^\/+/, '');
15
+ const resolved = path.resolve(wasiFilesystemRoot, rel);
16
+ const rootResolved = path.resolve(wasiFilesystemRoot) + path.sep;
17
+ if (resolved !== path.resolve(wasiFilesystemRoot) && !resolved.startsWith(rootResolved)) {
18
+ throw new Error(`wasi-path-traversal-refused: ${relPath} escapes ${wasiFilesystemRoot}`);
19
+ }
20
+ return resolved;
21
+ };
22
+ }
23
+
24
+ function createWasiShim(instanceRef, ctx) {
25
+ const { wasiFilesystemRoot, wasiOpenFiles, wasiNextFdRef, wasmAbortFlag, spoolDirForSentinel, currentVerbContextRef } = ctx;
26
+ const wasiResolvePath = makeWasiResolvePath(wasiFilesystemRoot);
27
+ const getMemory = () => instanceRef.value.exports.memory.buffer;
28
+ const shim = {
29
+ proc_exit: (code) => {
30
+ wasmAbortFlag.aborted = true;
31
+ wasmAbortFlag.code = code;
32
+ try {
33
+ const spoolDir = spoolDirForSentinel();
34
+ fs.mkdirSync(spoolDir, { recursive: true });
35
+ fs.writeFileSync(path.join(spoolDir, '.wasm-abort.json'), JSON.stringify({
36
+ ts: Date.now(),
37
+ exit_code: code,
38
+ verb_in_flight: currentVerbContextRef.value,
39
+ }));
40
+ } catch (_) {}
41
+ try { console.error(`[plugkit-wasm] wasm proc_exit(${code}) intercepted; throwing to abort current verb without killing watcher`); } catch (_) {}
42
+ throw new Error(`wasm proc_exit(${code}) during verb ${currentVerbContextRef.value && currentVerbContextRef.value.verb}`);
43
+ },
44
+ fd_write: (fd, iovs_ptr, iovs_len, nwritten_ptr) => {
45
+ try {
46
+ const buf = getMemory();
47
+ const dv = new DataView(buf);
48
+ const chunks = [];
49
+ let total = 0;
50
+ const iovsBase = iovs_ptr >>> 0; // >>>0: high-bit iovs pointer is negative in JS -> getUint32 would throw
51
+ for (let i = 0; i < iovs_len; i++) {
52
+ const base = iovsBase + i * 8;
53
+ const ptr = dv.getUint32(base, true);
54
+ const len = dv.getUint32(base + 4, true);
55
+ if (len > 0 && ptr + len <= buf.byteLength) {
56
+ chunks.push(new Uint8Array(buf, ptr, len).slice());
57
+ total += len;
58
+ }
59
+ }
60
+ const merged = new Uint8Array(total);
61
+ let off = 0;
62
+ for (const c of chunks) { merged.set(c, off); off += c.length; }
63
+ const text = new TextDecoder('utf-8').decode(merged);
64
+ if (fd === 2) process.stderr.write(text);
65
+ else process.stdout.write(text);
66
+ new DataView(getMemory()).setUint32(nwritten_ptr, total, true);
67
+ return 0;
68
+ } catch (e) {
69
+ return 28;
70
+ }
71
+ },
72
+ random_get: (buf_ptr, buf_len) => {
73
+ try {
74
+ crypto.randomFillSync(new Uint8Array(getMemory(), buf_ptr >>> 0, buf_len >>> 0)); // >>>0: high-bit ptr is negative in JS
75
+ return 0;
76
+ } catch (e) {
77
+ return 28;
78
+ }
79
+ },
80
+ clock_time_get: (clock_id, precision, time_ptr) => {
81
+ try {
82
+ const ns = BigInt(Date.now()) * 1000000n;
83
+ new DataView(getMemory()).setBigUint64(time_ptr >>> 0, ns, true); // >>>0: high-bit ptr is negative in JS
84
+ return 0;
85
+ } catch (e) {
86
+ return 28;
87
+ }
88
+ },
89
+ environ_get: () => 0,
90
+ environ_sizes_get: () => 0,
91
+ fd_prestat_get: (fd, buf_ptr) => {
92
+ if (fd !== 3) return 8;
93
+ try {
94
+ const dv = new DataView(getMemory());
95
+ dv.setUint8(buf_ptr, 0);
96
+ dv.setUint32(buf_ptr + 4, 1, true);
97
+ return 0;
98
+ } catch (e) { return 8; }
99
+ },
100
+ fd_prestat_dir_name: (fd, path_ptr, path_len) => {
101
+ if (fd !== 3) return 8;
102
+ try {
103
+ const buf = getMemory();
104
+ new Uint8Array(buf, path_ptr >>> 0, Math.min(path_len, 1)).set([0x2e]);
105
+ return 0;
106
+ } catch (e) { return 8; }
107
+ },
108
+ fd_close: (fd) => {
109
+ const entry = wasiOpenFiles.get(fd);
110
+ if (!entry) return 0;
111
+ try { fs.closeSync(entry.nodeFd); } catch (_) {}
112
+ wasiOpenFiles.delete(fd);
113
+ return 0;
114
+ },
115
+ fd_fdstat_get: (fd, stat_ptr) => {
116
+ try {
117
+ const dv = new DataView(getMemory());
118
+ const entry = wasiOpenFiles.get(fd);
119
+ dv.setUint8(stat_ptr, entry ? 4 : 0);
120
+ dv.setUint8(stat_ptr + 1, 0);
121
+ dv.setBigUint64(stat_ptr + 8, 0xffffffffffffffffn, true);
122
+ dv.setBigUint64(stat_ptr + 16, 0xffffffffffffffffn, true);
123
+ return 0;
124
+ } catch (e) { return 8; }
125
+ },
126
+ fd_fdstat_set_flags: () => 0,
127
+ fd_filestat_get: (fd, buf_ptr) => {
128
+ const entry = wasiOpenFiles.get(fd);
129
+ if (!entry) { if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] fd_filestat_get FAILED: no entry for fd=${fd}`); return 8; }
130
+ try {
131
+ const st = fs.fstatSync(entry.nodeFd);
132
+ const dv = new DataView(getMemory());
133
+ dv.setBigUint64(buf_ptr, 0n, true);
134
+ dv.setBigUint64(buf_ptr + 8, 0n, true);
135
+ dv.setUint8(buf_ptr + 16, 4);
136
+ dv.setBigUint64(buf_ptr + 24, 1n, true);
137
+ dv.setBigUint64(buf_ptr + 32, BigInt(st.size), true);
138
+ dv.setBigUint64(buf_ptr + 40, BigInt(Math.floor(st.atimeMs * 1e6)), true);
139
+ dv.setBigUint64(buf_ptr + 48, BigInt(Math.floor(st.mtimeMs * 1e6)), true);
140
+ dv.setBigUint64(buf_ptr + 56, BigInt(Math.floor(st.ctimeMs * 1e6)), true);
141
+ return 0;
142
+ } catch (e) { if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] fd_filestat_get FAILED: ${e && e.message}`); return 8; }
143
+ },
144
+ fd_seek: (fd, offset64, whence, newoffset_ptr) => {
145
+ const entry = wasiOpenFiles.get(fd);
146
+ if (!entry) { try { new DataView(getMemory()).setBigUint64(newoffset_ptr, 0n, true); } catch (_) {} return 8; }
147
+ try {
148
+ const offset = BigInt.asIntN(64, BigInt(offset64));
149
+ let base;
150
+ if (whence === 0) base = 0n;
151
+ else if (whence === 1) base = BigInt(entry.pos);
152
+ else base = BigInt(fs.fstatSync(entry.nodeFd).size);
153
+ const next = base + offset;
154
+ entry.pos = Number(next < 0n ? 0n : next);
155
+ new DataView(getMemory()).setBigUint64(newoffset_ptr, BigInt(entry.pos), true);
156
+ return 0;
157
+ } catch (e) { if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] fd_seek FAILED: ${e && e.message}`); return 8; }
158
+ },
159
+ fd_read: (fd, iovs_ptr, iovs_len, nread_ptr) => {
160
+ const entry = wasiOpenFiles.get(fd);
161
+ if (!entry) { try { new DataView(getMemory()).setUint32(nread_ptr, 0, true); } catch (_) {} return 8; }
162
+ try {
163
+ const buf = getMemory();
164
+ const dv = new DataView(buf);
165
+ let total = 0;
166
+ const iovsBase = iovs_ptr >>> 0;
167
+ for (let i = 0; i < iovs_len; i++) {
168
+ const base = iovsBase + i * 8;
169
+ const ptr = dv.getUint32(base, true) >>> 0;
170
+ const len = dv.getUint32(base + 4, true) >>> 0;
171
+ if (len === 0) continue;
172
+ const dest = Buffer.from(buf, ptr, len);
173
+ const n = fs.readSync(entry.nodeFd, dest, 0, len, entry.pos);
174
+ entry.pos += n;
175
+ total += n;
176
+ if (n < len) break;
177
+ }
178
+ dv.setUint32(nread_ptr, total, true);
179
+ return 0;
180
+ } catch (e) { return 8; }
181
+ },
182
+ fd_pread: (fd, iovs_ptr, iovs_len, offset64, nread_ptr) => {
183
+ const entry = wasiOpenFiles.get(fd);
184
+ if (!entry) { try { new DataView(getMemory()).setUint32(nread_ptr, 0, true); } catch (_) {} return 8; }
185
+ try {
186
+ const offset = Number(BigInt.asUintN(64, BigInt(offset64)));
187
+ const buf = getMemory();
188
+ const dv = new DataView(buf);
189
+ let total = 0;
190
+ const iovsBase = iovs_ptr >>> 0;
191
+ let pos = offset;
192
+ for (let i = 0; i < iovs_len; i++) {
193
+ const base = iovsBase + i * 8;
194
+ const ptr = dv.getUint32(base, true) >>> 0;
195
+ const len = dv.getUint32(base + 4, true) >>> 0;
196
+ if (len === 0) continue;
197
+ const dest = Buffer.from(buf, ptr, len);
198
+ const n = fs.readSync(entry.nodeFd, dest, 0, len, pos);
199
+ pos += n;
200
+ total += n;
201
+ if (n < len) break;
202
+ }
203
+ dv.setUint32(nread_ptr, total, true);
204
+ return 0;
205
+ } catch (e) { if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] fd_pread FAILED: ${e && e.message}`); return 8; }
206
+ },
207
+ fd_pwrite: (fd, iovs_ptr, iovs_len, offset64, nwritten_ptr) => {
208
+ const entry = wasiOpenFiles.get(fd);
209
+ if (!entry) { try { new DataView(getMemory()).setUint32(nwritten_ptr, 0, true); } catch (_) {} return 8; }
210
+ try {
211
+ const offset = Number(BigInt.asUintN(64, BigInt(offset64)));
212
+ const buf = getMemory();
213
+ const dv = new DataView(buf);
214
+ let total = 0;
215
+ const iovsBase = iovs_ptr >>> 0;
216
+ let pos = offset;
217
+ for (let i = 0; i < iovs_len; i++) {
218
+ const base = iovsBase + i * 8;
219
+ const ptr = dv.getUint32(base, true) >>> 0;
220
+ const len = dv.getUint32(base + 4, true) >>> 0;
221
+ if (len === 0) continue;
222
+ const src = Buffer.from(buf, ptr, len);
223
+ const n = fs.writeSync(entry.nodeFd, src, 0, len, pos);
224
+ pos += n;
225
+ total += n;
226
+ }
227
+ dv.setUint32(nwritten_ptr, total, true);
228
+ return 0;
229
+ } catch (e) { if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] fd_pwrite FAILED: ${e && e.message}`); return 8; }
230
+ },
231
+ fd_sync: (fd) => {
232
+ const entry = wasiOpenFiles.get(fd);
233
+ if (!entry) return 8;
234
+ try { fs.fsyncSync(entry.nodeFd); return 0; } catch (e) { if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] fd_sync FAILED: ${e && e.message}`); return 8; }
235
+ },
236
+ fd_datasync: (fd) => {
237
+ const entry = wasiOpenFiles.get(fd);
238
+ if (!entry) return 8;
239
+ try { fs.fdatasyncSync(entry.nodeFd); return 0; } catch (e) { return 8; }
240
+ },
241
+ fd_filestat_set_size: (fd, size64) => {
242
+ const entry = wasiOpenFiles.get(fd);
243
+ if (!entry) return 8;
244
+ try {
245
+ const size = Number(BigInt.asUintN(64, BigInt(size64)));
246
+ fs.ftruncateSync(entry.nodeFd, size);
247
+ return 0;
248
+ } catch (e) { if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] fd_filestat_set_size FAILED: ${e && e.message}`); return 8; }
249
+ },
250
+ path_create_directory: (_dirfd, path_ptr, path_len) => {
251
+ try {
252
+ const buf = getMemory();
253
+ const relPath = new TextDecoder('utf-8').decode(new Uint8Array(buf, path_ptr >>> 0, path_len >>> 0));
254
+ const absPath = wasiResolvePath(relPath);
255
+ fs.mkdirSync(absPath, { recursive: true });
256
+ return 0;
257
+ } catch (e) {
258
+ if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] path_create_directory FAILED: ${e && e.message}`);
259
+ return e && e.code === 'EEXIST' ? 0 : 8;
260
+ }
261
+ },
262
+ path_unlink_file: (_dirfd, path_ptr, path_len) => {
263
+ try {
264
+ const buf = getMemory();
265
+ const relPath = new TextDecoder('utf-8').decode(new Uint8Array(buf, path_ptr >>> 0, path_len >>> 0));
266
+ const absPath = wasiResolvePath(relPath);
267
+ fs.unlinkSync(absPath);
268
+ return 0;
269
+ } catch (e) {
270
+ return e && e.code === 'ENOENT' ? 44 : 8;
271
+ }
272
+ },
273
+ path_remove_directory: (_dirfd, path_ptr, path_len) => {
274
+ try {
275
+ const buf = getMemory();
276
+ const relPath = new TextDecoder('utf-8').decode(new Uint8Array(buf, path_ptr >>> 0, path_len >>> 0));
277
+ const absPath = wasiResolvePath(relPath);
278
+ fs.rmdirSync(absPath);
279
+ return 0;
280
+ } catch (e) {
281
+ if (e && e.code === 'ENOENT') return 44;
282
+ if (e && e.code === 'ENOTEMPTY') return 55;
283
+ return 8;
284
+ }
285
+ },
286
+ path_filestat_set_times: (_dirfd, _flags, path_ptr, path_len, atim64, mtim64, fst_flags) => {
287
+ try {
288
+ const buf = getMemory();
289
+ const relPath = new TextDecoder('utf-8').decode(new Uint8Array(buf, path_ptr >>> 0, path_len >>> 0));
290
+ const absPath = wasiResolvePath(relPath);
291
+ const FILESTAT_SET_ATIM = 0x1, FILESTAT_SET_ATIM_NOW = 0x2, FILESTAT_SET_MTIM = 0x4, FILESTAT_SET_MTIM_NOW = 0x8;
292
+ const st = fs.statSync(absPath);
293
+ const nowMs = Date.now();
294
+ let atimeMs = st.atimeMs;
295
+ let mtimeMs = st.mtimeMs;
296
+ if (fst_flags & FILESTAT_SET_ATIM_NOW) atimeMs = nowMs;
297
+ else if (fst_flags & FILESTAT_SET_ATIM) atimeMs = Number(BigInt.asUintN(64, BigInt(atim64))) / 1e6;
298
+ if (fst_flags & FILESTAT_SET_MTIM_NOW) mtimeMs = nowMs;
299
+ else if (fst_flags & FILESTAT_SET_MTIM) mtimeMs = Number(BigInt.asUintN(64, BigInt(mtim64))) / 1e6;
300
+ fs.utimesSync(absPath, atimeMs / 1000, mtimeMs / 1000);
301
+ return 0;
302
+ } catch (e) {
303
+ if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] path_filestat_set_times FAILED: ${e && e.message}`);
304
+ return e && e.code === 'ENOENT' ? 44 : 8;
305
+ }
306
+ },
307
+ path_open: (_dirfd, _dirflags, path_ptr, path_len, oflags, _rights_base, _rights_inherit, fdflags, opened_fd_ptr) => {
308
+ try {
309
+ const buf = getMemory();
310
+ const relPath = new TextDecoder('utf-8').decode(new Uint8Array(buf, path_ptr >>> 0, path_len >>> 0));
311
+ const absPath = wasiResolvePath(relPath);
312
+ fs.mkdirSync(path.dirname(absPath), { recursive: true });
313
+ const OFLAGS_CREAT = 1, OFLAGS_EXCL = 2, OFLAGS_TRUNC = 8;
314
+ let nodeFlags = 'r+';
315
+ const creat = (oflags & OFLAGS_CREAT) !== 0;
316
+ const excl = (oflags & OFLAGS_EXCL) !== 0;
317
+ const trunc = (oflags & OFLAGS_TRUNC) !== 0;
318
+ if (excl && creat) nodeFlags = 'wx+';
319
+ else if (trunc) nodeFlags = 'w+';
320
+ else if (creat) nodeFlags = fs.existsSync(absPath) ? 'r+' : 'w+';
321
+ else nodeFlags = 'r+';
322
+ if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] path_open: rel=${relPath} abs=${absPath} oflags=${oflags} nodeFlags=${nodeFlags}`);
323
+ const nodeFd = fs.openSync(absPath, nodeFlags);
324
+ const wasiFd = wasiNextFdRef.value++;
325
+ wasiOpenFiles.set(wasiFd, { nodeFd, pos: 0, path: absPath });
326
+ new DataView(buf).setUint32(opened_fd_ptr, wasiFd, true);
327
+ return 0;
328
+ } catch (e) {
329
+ if (process.env.PLUGKIT_DEBUG) console.error(`[plugkit-wasm] path_open FAILED: ${e && e.message}`);
330
+ return e && /ENOENT/.test(e.code || '') ? 44 : 8;
331
+ }
332
+ },
333
+ path_filestat_get: (_dirfd, _flags, path_ptr, path_len, buf_ptr) => {
334
+ try {
335
+ const buf = getMemory();
336
+ const relPath = new TextDecoder('utf-8').decode(new Uint8Array(buf, path_ptr >>> 0, path_len >>> 0));
337
+ const absPath = wasiResolvePath(relPath);
338
+ const st = fs.statSync(absPath);
339
+ const dv = new DataView(buf);
340
+ dv.setBigUint64(buf_ptr, 0n, true);
341
+ dv.setBigUint64(buf_ptr + 8, 0n, true);
342
+ dv.setUint8(buf_ptr + 16, st.isDirectory() ? 3 : 4);
343
+ dv.setBigUint64(buf_ptr + 24, 1n, true);
344
+ dv.setBigUint64(buf_ptr + 32, BigInt(st.size), true);
345
+ dv.setBigUint64(buf_ptr + 40, BigInt(Math.floor(st.atimeMs * 1e6)), true);
346
+ dv.setBigUint64(buf_ptr + 48, BigInt(Math.floor(st.mtimeMs * 1e6)), true);
347
+ dv.setBigUint64(buf_ptr + 56, BigInt(Math.floor(st.ctimeMs * 1e6)), true);
348
+ return 0;
349
+ } catch (e) {
350
+ return e && /ENOENT/.test(e.code || '') ? 44 : 8;
351
+ }
352
+ },
353
+ poll_oneoff: () => 0,
354
+ sched_yield: () => 0,
355
+ };
356
+ if (process.env.PLUGKIT_DEBUG_WASI) {
357
+ for (const k of Object.keys(shim)) {
358
+ const orig = shim[k];
359
+ shim[k] = (...args) => {
360
+ const r = orig(...args);
361
+ try { console.error(`[plugkit-wasm] wasi.${k}(${args.map(a => typeof a === 'bigint' ? a.toString() : a).join(',')}) -> ${r}`); } catch (_) {}
362
+ return r;
363
+ };
364
+ }
365
+ }
366
+ return new Proxy(shim, {
367
+ get(target, prop) {
368
+ if (prop in target) return target[prop];
369
+ return (...args) => {
370
+ console.error(`[plugkit-wasm] unimplemented WASI call: ${String(prop)} args=${args.length}`);
371
+ return 8;
372
+ };
373
+ }
374
+ });
375
+ }
376
+
377
+ export { wasiFilesystemRootFor, makeWasiResolvePath, createWasiShim };
@@ -0,0 +1,71 @@
1
+ function guardWasmRange(buffer, ptr, len, where) {
2
+ const total = buffer.byteLength;
3
+ if (!Number.isInteger(ptr) || !Number.isInteger(len) || ptr < 0 || len < 0 || ptr + len > total) {
4
+ throw new Error(`wasm-memory-read-out-of-bounds at ${where}: ptr=${ptr} len=${len} buffer=${total} -- corrupt (ptr,len) from wasm, refusing the read instead of crashing the dispatch loop`);
5
+ }
6
+ }
7
+
8
+ function decodeWasmResult(instance, result, where) {
9
+ const u = BigInt.asUintN(64, BigInt(result));
10
+ const ptr = Number(u & 0xffffffffn);
11
+ const len = Number(u >> 32n);
12
+ if (ptr === 0 || len === 0) return '';
13
+ const buffer = instance.exports.memory.buffer;
14
+ guardWasmRange(buffer, ptr, len, where);
15
+ const out = new TextDecoder().decode(new Uint8Array(buffer, ptr, len));
16
+ try { instance.exports.plugkit_free(ptr, len); } catch (_) {}
17
+ return out;
18
+ }
19
+
20
+ function writeWasmInput(instance, bytes, where) {
21
+ if (bytes.length === 0) return 0;
22
+ const ptr = instance.exports.plugkit_alloc(bytes.length) >>> 0;
23
+ if (ptr === 0) throw new Error(`wasm-alloc-failed at ${where}: plugkit_alloc returned 0 (wasm OOM)`);
24
+ guardWasmRange(instance.exports.memory.buffer, ptr, bytes.length, `${where}:writeWasmInput`);
25
+ new Uint8Array(instance.exports.memory.buffer, ptr, bytes.length).set(bytes);
26
+ return ptr;
27
+ }
28
+
29
+ function readWasmBytes(instance, ptr, len) {
30
+ if (ptr === 0 || len === 0) return new Uint8Array(0);
31
+ const buffer = instance.exports.memory.buffer;
32
+ guardWasmRange(buffer, ptr, len, 'readWasmBytes');
33
+ return new Uint8Array(buffer, ptr, len).slice();
34
+ }
35
+
36
+ function readWasmStr(instance, ptr, len) {
37
+ if (ptr === 0 || len === 0) return '';
38
+ const buffer = instance.exports.memory.buffer;
39
+ guardWasmRange(buffer, ptr, len, 'readWasmStr');
40
+ const bytes = new Uint8Array(buffer, ptr, len);
41
+ return new TextDecoder('utf-8').decode(bytes);
42
+ }
43
+
44
+ function writeWasmBytes(instance, bytes) {
45
+ if (bytes.length === 0) return 0n;
46
+ const ptr = instance.exports.plugkit_alloc(bytes.length) >>> 0;
47
+ if (ptr === 0) return 0n;
48
+ guardWasmRange(instance.exports.memory.buffer, ptr, bytes.length, 'writeWasmBytes');
49
+ new Uint8Array(instance.exports.memory.buffer, ptr, bytes.length).set(bytes);
50
+ return (BigInt(ptr) & 0xffffffffn) | (BigInt(bytes.length) << 32n);
51
+ }
52
+
53
+ function writeWasmStr(instance, str) {
54
+ if (!str) return 0n;
55
+ return writeWasmBytes(instance, new TextEncoder().encode(str));
56
+ }
57
+
58
+ function writeWasmJson(instance, value) {
59
+ return writeWasmStr(instance, JSON.stringify(value));
60
+ }
61
+
62
+ export {
63
+ guardWasmRange,
64
+ decodeWasmResult,
65
+ writeWasmInput,
66
+ readWasmBytes,
67
+ readWasmStr,
68
+ writeWasmBytes,
69
+ writeWasmStr,
70
+ writeWasmJson,
71
+ };
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1990",
3
+ "version": "2.0.1992",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -17,5 +17,5 @@
17
17
  "publishConfig": {
18
18
  "access": "public"
19
19
  },
20
- "plugkitVersion": "0.1.915"
20
+ "plugkitVersion": "0.1.916"
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1990",
3
+ "version": "2.0.1992",
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",
@@ -38,7 +38,6 @@
38
38
  "bin/plugkit.wasm.sha256",
39
39
  "bin/plugkit-slim.wasm.sha256",
40
40
  "gm-plugkit/bootstrap.js",
41
- "gm-plugkit/browser-idle.js",
42
41
  "gm-plugkit/cli.js",
43
42
  "gm-plugkit/index.js",
44
43
  "gm-plugkit/package.json",
@@ -50,7 +49,7 @@
50
49
  "gm-plugkit/plugkit.sha256",
51
50
  "gm-plugkit/plugkit-slim.wasm.sha256",
52
51
  "gm-plugkit/instructions/",
53
- "gm-plugkit/scripts/",
52
+ "gm-plugkit/wrapper/",
54
53
  "AGENTS.md",
55
54
  "README.md",
56
55
  "gm.json"
@@ -1,13 +0,0 @@
1
- function selectIdleBrowserSessions(ports, now, limitMs) {
2
- const idle = [];
3
- if (!ports || typeof ports !== 'object') return idle;
4
- for (const [sid, entry] of Object.entries(ports)) {
5
- if (!entry || typeof entry !== 'object') continue;
6
- const lastUse = Number.isFinite(entry.lastUse) ? entry.lastUse : 0;
7
- const idleMs = now - lastUse;
8
- if (idleMs >= limitMs) idle.push({ sid, entry, idleMs });
9
- }
10
- return idle;
11
- }
12
-
13
- module.exports = { selectIdleBrowserSessions };