gm-plugkit 2.0.1091 → 2.0.1092
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/package.json +1 -1
- package/plugkit-wasm-wrapper.js +364 -52
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1092",
|
|
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": {
|
package/plugkit-wasm-wrapper.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
const ptr =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
73
|
-
const
|
|
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,
|
|
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
|
|
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
|
-
|
|
82
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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);
|