gm-skill 2.0.1091 → 2.0.1093

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 CHANGED
@@ -28,7 +28,7 @@ npx gm-skill-bootstrap
28
28
 
29
29
  ## Version
30
30
 
31
- `2.0.1091` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` republishes this package alongside all 15 platform packages.
31
+ `2.0.1093` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` republishes this package alongside all 15 platform packages.
32
32
 
33
33
  ## Source of truth
34
34
 
package/bin/plugkit.js CHANGED
@@ -1,10 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- // Hot path: spawnSync to ~/.claude/gm-tools/plugkit.exe with inherited stdio.
4
- // Cold path (session-start / prompt-submit OR missing binary): synchronously
5
- // ensure gm-tools/plugkit{.exe} matches the pinned version, then run hook.
6
- // Cache-aware: when local matches the pin (sha-checked), zero network calls.
7
-
8
3
  const { spawnSync } = require('child_process');
9
4
  const path = require('path');
10
5
  const fs = require('fs');
@@ -12,12 +7,6 @@ const os = require('os');
12
7
 
13
8
  const wrapperDir = __dirname;
14
9
 
15
- function toolsBin() {
16
- const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
17
- const exe = process.platform === 'win32' ? 'plugkit.exe' : 'plugkit';
18
- return path.join(home, '.claude', 'gm-tools', exe);
19
- }
20
-
21
10
  function sha256OfFileSync(filePath) {
22
11
  try {
23
12
  const crypto = require('crypto');
@@ -32,14 +21,6 @@ function sha256OfFileSync(filePath) {
32
21
  } catch (_) { return null; }
33
22
  }
34
23
 
35
- function platformAsset() {
36
- const p = process.platform;
37
- const a = process.arch;
38
- if (p === 'win32') return a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe';
39
- if (p === 'darwin') return a === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64';
40
- return (a === 'arm64' || a === 'aarch64') ? 'plugkit-linux-arm64' : 'plugkit-linux-x64';
41
- }
42
-
43
24
  function readPinnedVersion() {
44
25
  try { return fs.readFileSync(path.join(wrapperDir, 'plugkit.version'), 'utf8').trim(); } catch (_) { return null; }
45
26
  }
@@ -1 +1 @@
1
- 0.1.376
1
+ 0.1.377
package/bin/plugkit.wasm CHANGED
Binary file
@@ -1 +1 @@
1
- 858e0b66294f2799a0e14000dc2581f6937516ae11f9d3f5de9f13f3a3fa350c plugkit.wasm
1
+ 26cc527cd72ee8d53711020c5f2bbcd9fad8c252329e5c72bb3ea1434ff4480c plugkit.wasm
@@ -2,44 +2,329 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import { watch } from 'fs';
5
+ import { spawn, spawnSync } from 'child_process';
6
+
7
+ const KV_DIR = path.join(os.homedir(), '.claude', 'gm-tools', 'kv');
8
+ fs.mkdirSync(KV_DIR, { recursive: true });
9
+
10
+ const RS_LEARN_URL = process.env.RS_LEARN_URL || 'http://127.0.0.1:8000';
11
+ const VEC_K_DEFAULT = 10;
12
+
13
+ const browserSessions = new Map();
14
+ let nextBrowserSessionId = 1;
5
15
 
6
16
  function createWasiShim() {
7
- const shim = new Proxy({}, {
17
+ return new Proxy({}, {
8
18
  get(target, prop) {
9
19
  if (prop === 'proc_exit') return (code) => process.exit(code);
20
+ if (prop === 'fd_write') return () => 0;
21
+ if (prop === 'environ_get') return () => 0;
22
+ if (prop === 'environ_sizes_get') return () => 0;
10
23
  return () => 0;
11
24
  }
12
25
  });
13
- return shim;
14
26
  }
15
27
 
16
- function memWriteStr(memory, ptr, len, str) {
17
- const bytes = new TextEncoder().encode(str);
18
- const buf = new Uint8Array(memory.buffer, ptr, len);
19
- buf.set(bytes.slice(0, len));
20
- return Math.min(bytes.length, len);
28
+ function readWasmBytes(instance, ptr, len) {
29
+ if (ptr === 0 || len === 0) return new Uint8Array(0);
30
+ return new Uint8Array(instance.exports.memory.buffer, ptr, len).slice();
21
31
  }
22
32
 
23
- function memReadStr(memory, ptr, len) {
24
- const buf = new Uint8Array(memory.buffer, ptr, len);
25
- return new TextDecoder().decode(buf);
33
+ function readWasmStr(instance, ptr, len) {
34
+ if (ptr === 0 || len === 0) return '';
35
+ const bytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
36
+ return new TextDecoder('utf-8').decode(bytes);
26
37
  }
27
38
 
28
- let nextMemPtr = 0x10000;
29
- function allocInMemory(memory, data) {
30
- const ptr = nextMemPtr;
31
- const buf = new Uint8Array(memory.buffer, ptr, data.length);
32
- buf.set(data);
33
- nextMemPtr += data.length;
34
- if (nextMemPtr > memory.buffer.byteLength - 4096) nextMemPtr = 0x10000;
35
- return { ptr, len: data.length };
39
+ function writeWasmBytes(instance, bytes) {
40
+ if (bytes.length === 0) return 0n;
41
+ const ptr = instance.exports.plugkit_alloc(bytes.length);
42
+ if (ptr === 0) return 0n;
43
+ new Uint8Array(instance.exports.memory.buffer, ptr, bytes.length).set(bytes);
44
+ return (BigInt(ptr) & 0xffffffffn) | (BigInt(bytes.length) << 32n);
36
45
  }
37
46
 
38
- function packResult(str) {
39
- const bytes = new TextEncoder().encode(str);
40
- const ptr = Math.random() * 0x100000 | 0;
41
- const len = bytes.length;
42
- return (BigInt(ptr) & 0xffffffffn) | (BigInt(len) << 32n);
47
+ function writeWasmStr(instance, str) {
48
+ if (!str) return 0n;
49
+ return writeWasmBytes(instance, new TextEncoder().encode(str));
50
+ }
51
+
52
+ function writeWasmJson(instance, value) {
53
+ return writeWasmStr(instance, JSON.stringify(value));
54
+ }
55
+
56
+ function kvFilePath(ns, key) {
57
+ const safeNs = String(ns).replace(/[^A-Za-z0-9._-]/g, '_');
58
+ const safeKey = String(key).replace(/[^A-Za-z0-9._-]/g, '_');
59
+ const dir = path.join(KV_DIR, safeNs);
60
+ fs.mkdirSync(dir, { recursive: true });
61
+ return path.join(dir, safeKey + '.json');
62
+ }
63
+
64
+ function makeHostFunctions(instanceRef) {
65
+ return {
66
+ host_fs_read: (pathPtr, pathLen) => {
67
+ try {
68
+ const filePath = readWasmStr(instanceRef.value, pathPtr, pathLen);
69
+ if (!filePath) return 0n;
70
+ const data = fs.readFileSync(filePath, 'utf-8');
71
+ return writeWasmStr(instanceRef.value, data);
72
+ } catch (e) {
73
+ return 0n;
74
+ }
75
+ },
76
+
77
+ host_fs_write: (pathPtr, pathLen, dataPtr, dataLen) => {
78
+ try {
79
+ const filePath = readWasmStr(instanceRef.value, pathPtr, pathLen);
80
+ const data = readWasmStr(instanceRef.value, dataPtr, dataLen);
81
+ if (!filePath) return 0;
82
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
83
+ fs.writeFileSync(filePath, data);
84
+ return 1;
85
+ } catch (e) {
86
+ return 0;
87
+ }
88
+ },
89
+
90
+ host_fs_readdir: (pathPtr, pathLen) => {
91
+ try {
92
+ const dirPath = readWasmStr(instanceRef.value, pathPtr, pathLen);
93
+ if (!dirPath) return 0n;
94
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true }).map(e => ({
95
+ name: e.name,
96
+ is_dir: e.isDirectory(),
97
+ is_file: e.isFile(),
98
+ }));
99
+ return writeWasmJson(instanceRef.value, entries);
100
+ } catch (e) {
101
+ return 0n;
102
+ }
103
+ },
104
+
105
+ host_fs_stat: (pathPtr, pathLen) => {
106
+ try {
107
+ const filePath = readWasmStr(instanceRef.value, pathPtr, pathLen);
108
+ if (!filePath) return 0n;
109
+ const s = fs.statSync(filePath);
110
+ return writeWasmJson(instanceRef.value, {
111
+ is_dir: s.isDirectory(),
112
+ is_file: s.isFile(),
113
+ size: s.size,
114
+ mtime_ms: s.mtimeMs,
115
+ });
116
+ } catch (e) {
117
+ return 0n;
118
+ }
119
+ },
120
+
121
+ host_fetch: (urlPtr, urlLen, optsPtr, optsLen) => {
122
+ try {
123
+ const url = readWasmStr(instanceRef.value, urlPtr, urlLen);
124
+ const optsStr = readWasmStr(instanceRef.value, optsPtr, optsLen);
125
+ const opts = optsStr ? JSON.parse(optsStr) : {};
126
+ const result = spawnSync(process.execPath, ['-e', `
127
+ const url = ${JSON.stringify(url)};
128
+ const opts = ${JSON.stringify(opts)};
129
+ fetch(url, opts).then(r => r.text().then(body => {
130
+ process.stdout.write(JSON.stringify({ status: r.status, body }));
131
+ })).catch(e => process.stdout.write(JSON.stringify({ status: 0, error: e.message })));
132
+ `], { encoding: 'utf-8', timeout: 10000 });
133
+ if (result.status !== 0) return writeWasmJson(instanceRef.value, { status: 0, error: result.stderr || 'fetch failed' });
134
+ return writeWasmStr(instanceRef.value, result.stdout || '{}');
135
+ } catch (e) {
136
+ return writeWasmJson(instanceRef.value, { status: 0, error: e.message });
137
+ }
138
+ },
139
+
140
+ host_kv_get: (nsPtr, nsLen, keyPtr, keyLen) => {
141
+ try {
142
+ const ns = readWasmStr(instanceRef.value, nsPtr, nsLen);
143
+ const key = readWasmStr(instanceRef.value, keyPtr, keyLen);
144
+ if (!ns || !key) return 0n;
145
+ const fp = kvFilePath(ns, key);
146
+ if (!fs.existsSync(fp)) return 0n;
147
+ const data = fs.readFileSync(fp, 'utf-8');
148
+ return writeWasmStr(instanceRef.value, data);
149
+ } catch (e) {
150
+ return 0n;
151
+ }
152
+ },
153
+
154
+ host_kv_put: (nsPtr, nsLen, keyPtr, keyLen, valPtr, valLen) => {
155
+ try {
156
+ const ns = readWasmStr(instanceRef.value, nsPtr, nsLen);
157
+ const key = readWasmStr(instanceRef.value, keyPtr, keyLen);
158
+ const val = readWasmStr(instanceRef.value, valPtr, valLen);
159
+ if (!ns || !key) return 0;
160
+ fs.writeFileSync(kvFilePath(ns, key), val);
161
+ return 1;
162
+ } catch (e) {
163
+ return 0;
164
+ }
165
+ },
166
+
167
+ host_kv_query: (nsPtr, nsLen, qPtr, qLen) => {
168
+ try {
169
+ const ns = readWasmStr(instanceRef.value, nsPtr, nsLen);
170
+ const q = readWasmStr(instanceRef.value, qPtr, qLen);
171
+ if (!ns) return 0n;
172
+ const dir = path.join(KV_DIR, String(ns).replace(/[^A-Za-z0-9._-]/g, '_'));
173
+ if (!fs.existsSync(dir)) return writeWasmJson(instanceRef.value, []);
174
+ const ql = q ? String(q).toLowerCase() : '';
175
+ const results = [];
176
+ for (const f of fs.readdirSync(dir)) {
177
+ if (!f.endsWith('.json')) continue;
178
+ const value = fs.readFileSync(path.join(dir, f), 'utf-8');
179
+ if (ql && !value.toLowerCase().includes(ql) && !f.toLowerCase().includes(ql)) continue;
180
+ results.push({ key: f.replace(/\.json$/, ''), value });
181
+ }
182
+ return writeWasmJson(instanceRef.value, results);
183
+ } catch (e) {
184
+ return 0n;
185
+ }
186
+ },
187
+
188
+ host_vec_search: (qPtr, qLen, k) => {
189
+ try {
190
+ const q = readWasmStr(instanceRef.value, qPtr, qLen);
191
+ if (!q) return writeWasmJson(instanceRef.value, []);
192
+ const k_ = k > 0 ? k : VEC_K_DEFAULT;
193
+ const body = JSON.stringify({ query: q, limit: k_, scope: 'episodes' });
194
+ const result = spawnSync(process.execPath, ['-e', `
195
+ fetch('${RS_LEARN_URL}/search', { method: 'POST', headers: { 'content-type': 'application/json' }, body: ${JSON.stringify(body)} })
196
+ .then(r => r.text().then(t => process.stdout.write(t)))
197
+ .catch(e => process.stdout.write(JSON.stringify({ error: e.message, results: [] })));
198
+ `], { encoding: 'utf-8', timeout: 5000 });
199
+ if (result.status !== 0 || !result.stdout) return writeWasmJson(instanceRef.value, []);
200
+ try {
201
+ const parsed = JSON.parse(result.stdout);
202
+ return writeWasmJson(instanceRef.value, parsed.results || parsed);
203
+ } catch (_) {
204
+ return writeWasmJson(instanceRef.value, []);
205
+ }
206
+ } catch (e) {
207
+ return writeWasmJson(instanceRef.value, []);
208
+ }
209
+ },
210
+
211
+ host_vec_embed: (textPtr, textLen) => {
212
+ try {
213
+ const text = readWasmStr(instanceRef.value, textPtr, textLen);
214
+ if (!text) return 0n;
215
+ const body = JSON.stringify({ text });
216
+ const result = spawnSync(process.execPath, ['-e', `
217
+ fetch('${RS_LEARN_URL}/embed', { method: 'POST', headers: { 'content-type': 'application/json' }, body: ${JSON.stringify(body)} })
218
+ .then(r => r.text().then(t => process.stdout.write(t)))
219
+ .catch(e => process.stdout.write(''));
220
+ `], { encoding: 'utf-8', timeout: 5000 });
221
+ if (result.status !== 0 || !result.stdout) return 0n;
222
+ return writeWasmStr(instanceRef.value, result.stdout);
223
+ } catch (e) {
224
+ return 0n;
225
+ }
226
+ },
227
+
228
+ host_browser_spawn: (urlPtr, urlLen) => {
229
+ try {
230
+ const url = readWasmStr(instanceRef.value, urlPtr, urlLen);
231
+ const id = BigInt(nextBrowserSessionId++);
232
+ browserSessions.set(id, { url, opened_at: Date.now() });
233
+ return id;
234
+ } catch (e) {
235
+ return 0n;
236
+ }
237
+ },
238
+
239
+ host_browser_eval: (sessionId, codePtr, codeLen) => {
240
+ try {
241
+ const code = readWasmStr(instanceRef.value, codePtr, codeLen);
242
+ const session = browserSessions.get(BigInt(sessionId));
243
+ if (!session) return writeWasmJson(instanceRef.value, { error: 'session not found' });
244
+ return writeWasmJson(instanceRef.value, { ok: false, error: 'browser eval not implemented in JS host; route via spool browser verb' });
245
+ } catch (e) {
246
+ return writeWasmJson(instanceRef.value, { error: e.message });
247
+ }
248
+ },
249
+
250
+ host_browser_close: (sessionId) => {
251
+ try {
252
+ return browserSessions.delete(BigInt(sessionId)) ? 1 : 0;
253
+ } catch (e) {
254
+ return 0;
255
+ }
256
+ },
257
+
258
+ host_exec_js: (codePtr, codeLen, optsPtr, optsLen) => {
259
+ try {
260
+ const code = readWasmStr(instanceRef.value, codePtr, codeLen);
261
+ const optsStr = readWasmStr(instanceRef.value, optsPtr, optsLen);
262
+ const opts = optsStr ? JSON.parse(optsStr) : {};
263
+ const lang = opts.lang || 'nodejs';
264
+ const cwd = opts.cwd || process.cwd();
265
+ const timeoutMs = opts.timeoutMs || 30000;
266
+ let cmd, args;
267
+ if (lang === 'nodejs' || lang === 'js') { cmd = process.execPath; args = ['-e', code]; }
268
+ else if (lang === 'python') { cmd = 'python'; args = ['-c', code]; }
269
+ else if (lang === 'bash') { cmd = 'bash'; args = ['-c', code]; }
270
+ else if (lang === 'deno') { cmd = 'deno'; args = ['eval', code]; }
271
+ else { return writeWasmJson(instanceRef.value, { ok: false, error: `unsupported lang: ${lang}` }); }
272
+ const result = spawnSync(cmd, args, { encoding: 'utf-8', timeout: timeoutMs, cwd, env: process.env });
273
+ return writeWasmJson(instanceRef.value, {
274
+ ok: result.status === 0,
275
+ stdout: result.stdout || '',
276
+ stderr: result.stderr || '',
277
+ exit_code: result.status === null ? -1 : result.status,
278
+ timed_out: result.signal === 'SIGTERM',
279
+ });
280
+ } catch (e) {
281
+ return writeWasmJson(instanceRef.value, { ok: false, error: e.message });
282
+ }
283
+ },
284
+
285
+ host_log: (level, msgPtr, msgLen) => {
286
+ try {
287
+ const msg = readWasmStr(instanceRef.value, msgPtr, msgLen);
288
+ const prefix = level >= 3 ? '[plugkit-wasm:err]' : level >= 2 ? '[plugkit-wasm:warn]' : '[plugkit-wasm]';
289
+ if (level >= 2) console.error(`${prefix} ${msg}`);
290
+ else console.log(`${prefix} ${msg}`);
291
+ return 0;
292
+ } catch (e) {
293
+ return 0;
294
+ }
295
+ },
296
+
297
+ host_now_ms: () => BigInt(Date.now()),
298
+
299
+ host_env_get: (keyPtr, keyLen) => {
300
+ try {
301
+ const key = readWasmStr(instanceRef.value, keyPtr, keyLen);
302
+ if (!key) return 0n;
303
+ const v = process.env[key];
304
+ if (v === undefined) return 0n;
305
+ return writeWasmStr(instanceRef.value, v);
306
+ } catch (e) {
307
+ return 0n;
308
+ }
309
+ },
310
+ };
311
+ }
312
+
313
+ function resolveVersion(instance) {
314
+ try {
315
+ return fs.readFileSync(path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit.version'), 'utf8').trim();
316
+ } catch (_) {}
317
+ try {
318
+ const fn = instance && instance.exports && instance.exports.plugkit_version;
319
+ if (typeof fn === 'function') {
320
+ const result = fn();
321
+ const ptr = Number(result & 0xffffffffn);
322
+ const len = Number(result >> 32n);
323
+ const bytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
324
+ return new TextDecoder().decode(bytes).trim();
325
+ }
326
+ } catch (_) {}
327
+ return 'unknown';
43
328
  }
44
329
 
45
330
  async function runSpoolWatcher(instance, spoolDir) {
@@ -48,6 +333,7 @@ async function runSpoolWatcher(instance, spoolDir) {
48
333
  fs.mkdirSync(inDir, { recursive: true });
49
334
  fs.mkdirSync(outDir, { recursive: true });
50
335
 
336
+ console.log(`[plugkit-wasm] plugkit v${resolveVersion(instance)} (wasm)`);
51
337
  console.log(`[plugkit-wasm] watching ${inDir}`);
52
338
 
53
339
  const processed = new Set();
@@ -69,22 +355,38 @@ async function runSpoolWatcher(instance, spoolDir) {
69
355
  const verbBytes = new TextEncoder().encode(verb);
70
356
  const bodyBytes = new TextEncoder().encode(body);
71
357
 
72
- const { ptr: verbPtr, len: verbLen } = allocInMemory(instance.exports.memory, verbBytes);
73
- const { ptr: bodyPtr, len: bodyLen } = allocInMemory(instance.exports.memory, bodyBytes);
358
+ const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
359
+ const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
360
+ new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
361
+ new Uint8Array(instance.exports.memory.buffer, bodyPtr, bodyBytes.length).set(bodyBytes);
74
362
 
75
- const result = dispatch(verbPtr, verbLen, bodyPtr, bodyLen);
363
+ const result = dispatch(verbPtr, verbBytes.length, bodyPtr, bodyBytes.length);
76
364
 
77
365
  const ptr = Number(result & 0xffffffffn);
78
366
  const len = Number(result >> 32n);
79
- const resultStr = memReadStr(instance.exports.memory, ptr, len);
367
+ const resultBytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
368
+ const resultStr = new TextDecoder().decode(resultBytes);
369
+
370
+ const taskBase = path.basename(filePath, path.extname(filePath));
371
+ const outName = dir === '.' ? `${taskBase}.json` : `${verb}-${taskBase}.json`;
372
+ fs.writeFileSync(path.join(outDir, outName), resultStr);
80
373
 
81
- const taskId = Math.random().toString(36).slice(2);
82
- fs.writeFileSync(path.join(outDir, `${taskId}.json`), resultStr);
374
+ try { instance.exports.plugkit_free(verbPtr, verbBytes.length); } catch (_) {}
375
+ try { instance.exports.plugkit_free(bodyPtr, bodyBytes.length); } catch (_) {}
376
+ try { instance.exports.plugkit_free(ptr, len); } catch (_) {}
83
377
 
84
378
  fs.unlinkSync(filePath);
85
379
  processed.delete(key);
86
380
  } catch (e) {
87
381
  console.error(`[plugkit-wasm] error processing ${key}: ${e.message}`);
382
+ const taskBase = path.basename(filePath, path.extname(filePath));
383
+ const relPath = path.relative(inDir, filePath);
384
+ const dir = path.dirname(relPath);
385
+ const verb = dir === '.' ? taskBase : dir;
386
+ const outName = dir === '.' ? `${taskBase}.json` : `${verb}-${taskBase}.json`;
387
+ try {
388
+ fs.writeFileSync(path.join(outDir, outName), JSON.stringify({ ok: false, error: e.message }));
389
+ } catch (_) {}
88
390
  try { fs.unlinkSync(filePath); } catch (_) {}
89
391
  processed.delete(key);
90
392
  }
@@ -108,6 +410,18 @@ async function runSpoolWatcher(instance, spoolDir) {
108
410
  return files;
109
411
  }
110
412
 
413
+ const heartbeatPath = path.join(spoolDir, '.watcher.heartbeat');
414
+ setInterval(() => {
415
+ try { fs.writeFileSync(heartbeatPath, String(Date.now())); } catch (_) {}
416
+ }, 5000);
417
+
418
+ const pollDeadline = setInterval(async () => {
419
+ const existing = walkDir(inDir);
420
+ for (const fullPath of existing) {
421
+ await processFile(fullPath);
422
+ }
423
+ }, 250);
424
+
111
425
  const existing = walkDir(inDir);
112
426
  for (const fullPath of existing) {
113
427
  await processFile(fullPath);
@@ -139,38 +453,20 @@ async function runSpoolWatcher(instance, spoolDir) {
139
453
  const wasmBuffer = fs.readFileSync(wasmPath);
140
454
  const wasmModule = new WebAssembly.Module(wasmBuffer);
141
455
 
142
- const memory = new WebAssembly.Memory({ initial: 256, maximum: 512 });
143
-
144
- const hostFunctions = {
145
- host_fs_read: () => 0,
146
- host_fs_write: () => 0,
147
- host_fs_readdir: () => 0,
148
- host_fs_stat: () => 0,
149
- host_kv_get: () => 0,
150
- host_kv_put: () => 0,
151
- host_kv_query: () => 0,
152
- host_fetch: () => 0,
153
- host_vec_search: () => 0,
154
- host_vec_embed: () => 0,
155
- host_browser_spawn: () => 0,
156
- host_browser_eval: () => 0,
157
- host_browser_close: () => 0,
158
- host_exec_js: () => 0,
159
- host_log: (ptr, len) => { console.log('[host_log]'); return 0; },
160
- host_now_ms: () => BigInt(Date.now()),
161
- host_env_get: () => 0,
162
- };
456
+ const instanceRef = { value: null };
457
+ const hostFunctions = makeHostFunctions(instanceRef);
163
458
 
164
459
  const importObject = {
165
- env: { memory, ...hostFunctions },
460
+ env: hostFunctions,
166
461
  wasi_snapshot_preview1: createWasiShim(),
167
462
  };
168
463
 
169
464
  const instance = new WebAssembly.Instance(wasmModule, importObject);
465
+ instanceRef.value = instance;
170
466
 
171
467
  const args = process.argv.slice(2);
172
468
  if (args.includes('--version')) {
173
- console.log('plugkit v0.1.366 (wasm)');
469
+ console.log(`plugkit v${resolveVersion(instance)} (wasm)`);
174
470
  process.exit(0);
175
471
  }
176
472
 
@@ -178,6 +474,22 @@ async function runSpoolWatcher(instance, spoolDir) {
178
474
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
179
475
  const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
180
476
  await runSpoolWatcher(instance, spoolDir);
477
+ } else if (args[0] === 'dispatch') {
478
+ const verb = args[1] || '';
479
+ const body = args[2] || '{}';
480
+ const dispatch = instance.exports.dispatch_verb;
481
+ const verbBytes = new TextEncoder().encode(verb);
482
+ const bodyBytes = new TextEncoder().encode(body);
483
+ const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
484
+ const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
485
+ new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
486
+ new Uint8Array(instance.exports.memory.buffer, bodyPtr, bodyBytes.length).set(bodyBytes);
487
+ const result = dispatch(verbPtr, verbBytes.length, bodyPtr, bodyBytes.length);
488
+ const ptr = Number(result & 0xffffffffn);
489
+ const len = Number(result >> 32n);
490
+ const out = new TextDecoder().decode(new Uint8Array(instance.exports.memory.buffer, ptr, len));
491
+ process.stdout.write(out);
492
+ process.exit(0);
181
493
  } else {
182
494
  console.log('[plugkit-wasm] args:', args.join(' '));
183
495
  process.exit(0);
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1091",
3
+ "version": "2.0.1093",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -23,5 +23,5 @@
23
23
  "publishConfig": {
24
24
  "access": "public"
25
25
  },
26
- "plugkitVersion": "0.1.376"
26
+ "plugkitVersion": "0.1.377"
27
27
  }
package/lang/browser.js CHANGED
@@ -1,27 +1,24 @@
1
1
  'use strict';
2
2
  const path = require('path');
3
3
  const os = require('os');
4
- const { spawnSync, execSync } = require('child_process');
4
+ const { spawnSync } = require('child_process');
5
5
  const fsSync = require('fs');
6
6
 
7
7
  function findPlugkit() {
8
8
  if (process.env.PLUGKIT_BIN && fsSync.existsSync(process.env.PLUGKIT_BIN)) return process.env.PLUGKIT_BIN;
9
9
  const home = os.homedir();
10
- const isWin = process.platform === 'win32';
11
- const exe = isWin ? 'plugkit.exe' : 'plugkit';
12
10
  const candidates = [
13
- path.join(home, '.claude', 'gm-tools', exe),
14
- path.join(home, '.local', 'bin', exe),
15
- path.join(home, '.claude', 'plugins', 'marketplaces', 'gm-cc', 'bin', exe),
11
+ path.join(__dirname, '..', 'bin', 'plugkit.js'),
12
+ path.join(home, '.claude', 'gm-tools', 'plugkit.js'),
13
+ path.join(home, '.claude', 'plugins', 'marketplaces', 'gm-cc', 'bin', 'plugkit.js'),
16
14
  ];
17
15
  if (process.env.CLAUDE_PLUGIN_ROOT) {
18
- candidates.push(path.join(process.env.CLAUDE_PLUGIN_ROOT, 'bin', exe));
16
+ candidates.push(path.join(process.env.CLAUDE_PLUGIN_ROOT, 'bin', 'plugkit.js'));
19
17
  }
20
18
  for (const p of candidates) {
21
19
  if (fsSync.existsSync(p)) return p;
22
20
  }
23
- try { execSync('plugkit --version', { encoding: 'utf-8', timeout: 3000 }); return 'plugkit'; } catch (_) {}
24
- return 'plugkit';
21
+ return null;
25
22
  }
26
23
 
27
24
  module.exports = {
@@ -32,8 +29,9 @@ module.exports = {
32
29
  try {
33
30
  fsSync.writeFileSync(tmp, code, 'utf-8');
34
31
  const plugkit = findPlugkit();
32
+ if (!plugkit) return '[plugkit wrapper not found]';
35
33
  const opts = { encoding: 'utf-8', timeout: 120000, windowsHide: true, ...(cwd && { cwd }) };
36
- const r = spawnSync(plugkit, ['exec', '--lang', 'browser', '--file', tmp], opts);
34
+ const r = spawnSync(process.execPath, [plugkit, 'exec', '--lang', 'browser', '--file', tmp], opts);
37
35
  const out = (r.stdout || '').trimEnd();
38
36
  const err = (r.stderr || '').trimEnd();
39
37
  return out && err ? out + '\n[stderr]\n' + err : out || err || '(no output)';
@@ -113,39 +113,54 @@ async function downloadPlugkitBinary(version) {
113
113
  });
114
114
  }
115
115
 
116
- function isProcessRunning(pidOrName) {
116
+ function findPlugkitWasmPids() {
117
+ const pids = [];
117
118
  try {
118
- const plat = getPlatformKey();
119
- if (plat === 'win32') {
120
- const output = execSync('tasklist /FO CSV /NH', { encoding: 'utf8' });
119
+ if (process.platform === 'win32') {
120
+ const output = execSync('wmic process where "Name=\'bun.exe\' or Name=\'node.exe\'" get ProcessId,CommandLine /FORMAT:CSV', { encoding: 'utf8' });
121
121
  const lines = output.split('\n').filter(Boolean);
122
- return lines.some(line => {
123
- const parts = line.split(',').map(p => p.trim().replace(/^"/, '').replace(/"$/, ''));
124
- return parts[0] === 'plugkit.exe' || parts[0] === pidOrName;
125
- });
122
+ for (const line of lines) {
123
+ if (!line.includes('plugkit-wasm-wrapper.js')) continue;
124
+ const parts = line.split(',');
125
+ const pid = parts[parts.length - 1].trim();
126
+ if (/^\d+$/.test(pid)) pids.push(pid);
127
+ }
126
128
  } else {
127
- try {
128
- execSync(`ps -p ${pidOrName} > /dev/null 2>&1`);
129
- return true;
130
- } catch {
131
- return false;
129
+ const output = execSync("ps -eo pid,args", { encoding: 'utf8' });
130
+ const lines = output.split('\n').filter(Boolean);
131
+ for (const line of lines) {
132
+ if (!line.includes('plugkit-wasm-wrapper')) continue;
133
+ const m = line.trim().match(/^(\d+)\s/);
134
+ if (m) pids.push(m[1]);
132
135
  }
133
136
  }
134
137
  } catch (e) {
135
- return false;
136
138
  }
139
+ return pids;
140
+ }
141
+
142
+ function isProcessRunning() {
143
+ return findPlugkitWasmPids().length > 0;
137
144
  }
138
145
 
139
146
  function killExistingPlugkit() {
140
147
  try {
141
- const plat = getPlatformKey();
142
- if (plat === 'win32') {
143
- execSync('taskkill /IM plugkit.exe /F 2>nul || true', { shell: true });
144
- emitBootstrapEvent('info', 'Killed existing plugkit process on Windows');
145
- } else {
146
- execSync('pkill -f "plugkit" || true', { shell: true });
147
- emitBootstrapEvent('info', 'Killed existing plugkit process on Unix');
148
+ const pids = findPlugkitWasmPids();
149
+ if (pids.length === 0) {
150
+ emitBootstrapEvent('info', 'No existing plugkit WASM watcher to kill');
151
+ return;
148
152
  }
153
+ for (const pid of pids) {
154
+ try {
155
+ if (process.platform === 'win32') {
156
+ execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
157
+ } else {
158
+ execSync(`kill -9 ${pid}`, { stdio: 'ignore' });
159
+ }
160
+ } catch (e) {
161
+ }
162
+ }
163
+ emitBootstrapEvent('info', 'Killed existing plugkit WASM watcher', { pids });
149
164
  } catch (e) {
150
165
  emitBootstrapEvent('warn', 'Failed to kill existing plugkit', { error: e.message });
151
166
  }
@@ -235,7 +250,7 @@ async function spawnPlugkitWatcher(wasmPath) {
235
250
  }
236
251
  }
237
252
 
238
- async function bootstrapPlugkit() {
253
+ async function bootstrapPlugkit(sessionId) {
239
254
  const startTime = Date.now();
240
255
 
241
256
  try {
@@ -251,18 +266,19 @@ async function bootstrapPlugkit() {
251
266
  if (!binaryMissing && !versionMismatch) {
252
267
  emitBootstrapEvent('info', 'Binary up-to-date', { version: installedVersion });
253
268
 
254
- if (isProcessRunning('plugkit')) {
269
+ if (isProcessRunning()) {
255
270
  emitBootstrapEvent('info', 'Plugkit watcher already running');
256
271
  const statusPayload = {
257
272
  ok: true,
258
273
  version: installedVersion,
259
274
  status: 'running',
275
+ sessionId: sessionId || null,
260
276
  timestamp: new Date().toISOString(),
261
277
  durationMs: Date.now() - startTime,
262
278
  };
263
279
  fs.mkdirSync(path.dirname(BOOTSTRAP_STATUS_FILE), { recursive: true });
264
280
  fs.writeFileSync(BOOTSTRAP_STATUS_FILE, JSON.stringify(statusPayload, null, 2));
265
- return { ok: true };
281
+ return { ok: true, binaryPath: PLUGKIT_WASM_PATH };
266
282
  }
267
283
  }
268
284
 
@@ -308,10 +324,10 @@ async function bootstrapPlugkit() {
308
324
  emitBootstrapEvent('warn', 'Binary health check failed, but proceeding');
309
325
  }
310
326
 
311
- const watcherRunning = isProcessRunning('plugkit');
327
+ const watcherRunning = isProcessRunning();
312
328
  let watcherPid;
313
329
  if (!watcherRunning) {
314
- watcherPid = await spawnPlugkitWatcher(plugkitPath);
330
+ watcherPid = await spawnPlugkitWatcher(PLUGKIT_WASM_PATH);
315
331
  } else {
316
332
  watcherPid = 'already-running';
317
333
  emitBootstrapEvent('info', 'Watcher already running');
@@ -322,6 +338,7 @@ async function bootstrapPlugkit() {
322
338
  ok: true,
323
339
  version: currentVersion,
324
340
  watcherPid,
341
+ sessionId: sessionId || null,
325
342
  timestamp: new Date().toISOString(),
326
343
  durationMs: Date.now() - startTime,
327
344
  };
@@ -330,7 +347,7 @@ async function bootstrapPlugkit() {
330
347
  fs.writeFileSync(BOOTSTRAP_STATUS_FILE, JSON.stringify(statusPayload, null, 2));
331
348
 
332
349
  emitBootstrapEvent('info', 'Bootstrap completed successfully', statusPayload);
333
- return { ok: true };
350
+ return { ok: true, binaryPath: PLUGKIT_WASM_PATH };
334
351
  } catch (err) {
335
352
  const errorPayload = {
336
353
  ok: false,
@@ -381,17 +398,12 @@ async function bootstrapAcptoapi() {
381
398
  }
382
399
 
383
400
  async function getSnapshot(sessionId, cwd) {
384
- const plugkitPath = getPlugkitPath();
385
- if (!fs.existsSync(plugkitPath)) {
386
- return { git: { ok: false }, tasks: [], error: 'plugkit not found' };
387
- }
388
-
389
401
  try {
390
402
  const sid = sessionId || process.env.CLAUDE_SESSION_ID || 'default';
391
403
  const c = cwd || process.cwd();
392
- const cmd = `"${plugkitPath}" snapshot --session "${sid}" --cwd "${c}"`;
393
- const output = execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
394
- return JSON.parse(output);
404
+ const result = await spool.execSpool('snapshot', 'snapshot', { sessionId: sid, cwd: c, timeoutMs: 5000 });
405
+ if (result && typeof result === 'object') return result;
406
+ return { git: { ok: false }, tasks: [], error: 'no snapshot result' };
395
407
  } catch (e) {
396
408
  emitBootstrapEvent('warn', 'Failed to get snapshot', { error: e.message });
397
409
  return { git: { ok: false }, tasks: [], error: e.message };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1091",
3
+ "version": "2.0.1093",
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.1091"
42
+ "gm-plugkit": "^2.0.1093"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"
@@ -1,7 +1,6 @@
1
1
  #!/bin/sh
2
2
  PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT}}"
3
3
  [ -z "$PLUGIN_ROOT" ] && exit 0
4
- PLUGKIT="$PLUGIN_ROOT/bin/plugkit"
5
- [ -f "$PLUGIN_ROOT/bin/plugkit.exe" ] && PLUGKIT="$PLUGIN_ROOT/bin/plugkit.exe"
6
- [ ! -f "$PLUGKIT" ] && exit 0
7
- "$PLUGKIT" hook "$1"
4
+ PLUGKIT_JS="$PLUGIN_ROOT/bin/plugkit.js"
5
+ [ ! -f "$PLUGKIT_JS" ] && exit 0
6
+ node "$PLUGKIT_JS" hook "$1"