gm-skill 2.0.2208 → 2.0.2210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/bin/bootstrap.js +21 -160
- package/bin/plugkit-slim.wasm.sha256 +1 -1
- package/bin/plugkit.version +1 -1
- package/bin/plugkit.wasm.sha256 +1 -1
- package/gm-plugkit/bootstrap-shared.js +320 -0
- package/gm-plugkit/bootstrap-wasm-core.js +236 -0
- package/gm-plugkit/bootstrap.js +23 -184
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-slim.wasm.sha256 +1 -1
- package/gm-plugkit/plugkit.version +1 -1
- package/gm.json +2 -2
- package/package.json +3 -1
package/AGENTS.md
CHANGED
|
@@ -30,7 +30,7 @@ Plugkit-core = the wasm cdylib guest (the gm brain), published as `plugkit.wasm`
|
|
|
30
30
|
|
|
31
31
|
**`agentplug-runner`** (repo `AnEntrypoint/agentplug`, published to `AnEntrypoint/agentplug-bin`) is a real native wasmtime binary that loads gm.wasm as one plugin among siblings (`bert`/`libsql`/`treesitter`), routing gm.wasm's `host_plugin_call`/`host_vec_embed` to those shared plugins -- browser and task are both native in agentplug-host, needing nothing from the retired JS wrapper. Full mechanics: the recall store (`recall: agentplug-runner wasmtime plugin loading mechanics`).
|
|
32
32
|
|
|
33
|
-
The `gm-plugkit` npm identity stays load-bearing as the thin launcher edge only: `bun x gm-plugkit@latest spool` (`cli.js::tryDelegateToRunner`) re-execs the `agentplug-runner` executable staged at `~/.gm-tools/agentplug-runner`
|
|
33
|
+
The `gm-plugkit` npm identity stays load-bearing as the thin launcher edge only: `bun x gm-plugkit@latest spool` (`cli.js::tryDelegateToRunner`) re-execs the `agentplug-runner` executable staged at `~/.gm-tools/agentplug-runner` and exits -- there is no silent no-loader state. The launcher-edge install path (`~/.gm-tools`) and the runner's own runtime state (`~/.agentplug`) are two different directories; always check `~/.agentplug/plugins/gm.version` for what is actually being served, never `~/.gm-tools`. Full path-split mechanics: the recall store (`recall: agentplug-runner runtime-state path split`). Size + embedded-model mechanics: the recall store (`recall: WASM-only plugkit size mechanics`).
|
|
34
34
|
|
|
35
35
|
**`bin/install.js` hard-requires agentplug-runner.** It downloads the sha256-verified native runner from `AnEntrypoint/agentplug-bin` for the host platform, and if none is published (or the download/verify fails) it fails the install loudly with a clear message rather than leaving the user with no loader. There is no JS-host fallback to silently fall through to anymore.
|
|
36
36
|
|
package/bin/bootstrap.js
CHANGED
|
@@ -5,42 +5,40 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
|
-
const { spawnSync } = require('child_process');
|
|
9
|
-
const { pidAlive, sha256OfFile, sha256OfFileSync } = require('../gm-plugkit/gm-process');
|
|
10
8
|
const shared = require('../gm-plugkit/bootstrap-shared');
|
|
9
|
+
const core = require('../gm-plugkit/bootstrap-wasm-core');
|
|
11
10
|
const {
|
|
12
11
|
obsEvent,
|
|
13
12
|
cacheRoot,
|
|
14
13
|
fallbackCacheRoot,
|
|
15
|
-
gmToolsDir,
|
|
16
14
|
ensureDir,
|
|
17
15
|
acquireLock,
|
|
18
16
|
releaseLock,
|
|
19
|
-
isLockStale,
|
|
20
|
-
pruneOldVersions,
|
|
21
17
|
healIfShaMatches,
|
|
22
|
-
|
|
18
|
+
pruneOldVersions,
|
|
23
19
|
readDaemonVersion,
|
|
24
20
|
writeDaemonVersion,
|
|
25
|
-
pidCommandLineForKillGuard,
|
|
26
|
-
pidIsPlugkitProcess,
|
|
27
|
-
writeKillAttribution,
|
|
28
|
-
killPid,
|
|
29
21
|
killSpoolWatcherInCwd,
|
|
30
22
|
proactiveKillForNewInstall,
|
|
31
23
|
ensureNextStepWiring,
|
|
32
|
-
resolveWindowsExe,
|
|
33
|
-
resolveNpmCliJs,
|
|
34
24
|
} = shared;
|
|
25
|
+
const {
|
|
26
|
+
readVersionFile,
|
|
27
|
+
readShaManifest,
|
|
28
|
+
resolveCacheWasmPath,
|
|
29
|
+
extractNpmPackageWithRetry: extractNpmPackageWithRetryCore,
|
|
30
|
+
writeBootstrapError,
|
|
31
|
+
clearBootstrapError,
|
|
32
|
+
} = core;
|
|
35
33
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
const log = core.makeLogger('plugkit-bootstrap');
|
|
35
|
+
|
|
36
|
+
function copyWasmToGmTools(wasmPath, wrapperDir, version) {
|
|
37
|
+
return core.copyWasmToGmTools(wasmPath, version, { wrapperDir });
|
|
38
|
+
}
|
|
41
39
|
|
|
42
|
-
function
|
|
43
|
-
|
|
40
|
+
function extractNpmPackageWithRetry(destPath, version) {
|
|
41
|
+
return extractNpmPackageWithRetryCore(destPath, version, { log });
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
function discoverBundledSkills(wrapperDir) {
|
|
@@ -126,139 +124,6 @@ function probeBinaryVersion(binPath) {
|
|
|
126
124
|
} catch (_) { return null; }
|
|
127
125
|
}
|
|
128
126
|
|
|
129
|
-
function writeBootstrapError(spec) {
|
|
130
|
-
try {
|
|
131
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
132
|
-
const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
|
|
133
|
-
fs.mkdirSync(spoolDir, { recursive: true });
|
|
134
|
-
const out = path.join(spoolDir, '.bootstrap-error.json');
|
|
135
|
-
fs.writeFileSync(out, JSON.stringify({ ts: new Date().toISOString(), ...spec }, null, 2));
|
|
136
|
-
} catch (_) {}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function clearBootstrapError() {
|
|
140
|
-
try {
|
|
141
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
142
|
-
const out = path.join(projectDir, '.gm', 'exec-spool', '.bootstrap-error.json');
|
|
143
|
-
fs.unlinkSync(out);
|
|
144
|
-
} catch (_) {}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function copyWasmToGmTools(wasmPath, wrapperDir, version) {
|
|
148
|
-
const dst = gmToolsDir();
|
|
149
|
-
fs.mkdirSync(dst, { recursive: true });
|
|
150
|
-
const target = path.join(dst, 'plugkit.wasm');
|
|
151
|
-
if (fs.existsSync(target)) {
|
|
152
|
-
try {
|
|
153
|
-
const cur = sha256OfFileSync(target);
|
|
154
|
-
const src = sha256OfFileSync(wasmPath);
|
|
155
|
-
if (cur === src) {
|
|
156
|
-
try { fs.writeFileSync(path.join(dst, 'plugkit.version'), version); } catch (_) {}
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
} catch (_) {}
|
|
160
|
-
}
|
|
161
|
-
fs.copyFileSync(wasmPath, target);
|
|
162
|
-
fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
|
|
163
|
-
try {
|
|
164
|
-
const srcSha = path.join(wrapperDir, 'plugkit.sha256');
|
|
165
|
-
if (fs.existsSync(srcSha)) fs.copyFileSync(srcSha, path.join(dst, 'plugkit.sha256'));
|
|
166
|
-
} catch (_) {}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function readVersionFile(wrapperDir) {
|
|
170
|
-
const p = path.join(wrapperDir, 'plugkit.version');
|
|
171
|
-
if (!fs.existsSync(p)) throw new Error(`plugkit.version not found at ${p}`);
|
|
172
|
-
return fs.readFileSync(p, 'utf8').trim();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
function readShaManifest(wrapperDir, manifestName) {
|
|
177
|
-
const p = path.join(wrapperDir, manifestName || 'plugkit.sha256');
|
|
178
|
-
if (!fs.existsSync(p)) return null;
|
|
179
|
-
const out = {};
|
|
180
|
-
for (const line of fs.readFileSync(p, 'utf8').split(/\r?\n/)) {
|
|
181
|
-
const m = line.match(/^([0-9a-f]{64})\s+(\S+)\s*$/i);
|
|
182
|
-
if (m) out[m[2]] = m[1].toLowerCase();
|
|
183
|
-
}
|
|
184
|
-
return out;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function extractNpmPackageWasm(destPath, version) {
|
|
188
|
-
const tempDir = path.join(path.dirname(destPath), '.npm-extract-' + Date.now());
|
|
189
|
-
try {
|
|
190
|
-
ensureDir(tempDir);
|
|
191
|
-
const startMs = Date.now();
|
|
192
|
-
log(`extracting npm package ${NPM_PACKAGE}@${version} to ${tempDir}`);
|
|
193
|
-
obsEvent('bootstrap', 'npm.extract.start', { package: NPM_PACKAGE, version });
|
|
194
|
-
|
|
195
|
-
const npmResolved = resolveWindowsExe('npm');
|
|
196
|
-
const isCmdShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(npmResolved);
|
|
197
|
-
const npmCliJs = isCmdShim ? resolveNpmCliJs(npmResolved) : null;
|
|
198
|
-
const installArgs = ['install', '--no-audit', '--no-fund', '--no-save', '--prefix', tempDir, NPM_PACKAGE + '@' + version];
|
|
199
|
-
|
|
200
|
-
const spawnCmd = npmCliJs ? process.execPath : (isCmdShim && /\s/.test(npmResolved) ? `"${npmResolved}"` : npmResolved);
|
|
201
|
-
const rawArgs = npmCliJs ? [npmCliJs, ...installArgs] : installArgs;
|
|
202
|
-
const spawnArgs = (isCmdShim && !npmCliJs) ? rawArgs.map(a => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a) : rawArgs;
|
|
203
|
-
const result = spawnSync(
|
|
204
|
-
spawnCmd,
|
|
205
|
-
spawnArgs,
|
|
206
|
-
{
|
|
207
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
208
|
-
timeout: ATTEMPT_TIMEOUT_MS,
|
|
209
|
-
encoding: 'utf8',
|
|
210
|
-
windowsHide: true,
|
|
211
|
-
...((isCmdShim && !npmCliJs) ? { shell: true } : {}),
|
|
212
|
-
}
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
if (result.error) throw result.error;
|
|
216
|
-
if (result.status !== 0) {
|
|
217
|
-
throw new Error(`npm install extraction failed: ${result.stderr || result.stdout || 'unknown error'}`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const nodeModulesPath = path.join(tempDir, 'node_modules', NPM_PACKAGE, 'plugkit.wasm');
|
|
221
|
-
if (!fs.existsSync(nodeModulesPath)) {
|
|
222
|
-
throw new Error(`plugkit.wasm not found in extracted npm package at ${nodeModulesPath}`);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
fs.copyFileSync(nodeModulesPath, destPath);
|
|
226
|
-
log(`extracted ${nodeModulesPath} -> ${destPath}`);
|
|
227
|
-
obsEvent('bootstrap', 'npm.extract.end', { dur_ms: Date.now() - startMs, ok: true });
|
|
228
|
-
} finally {
|
|
229
|
-
try { fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 1, retryDelay: 50 }); } catch (_) {}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function extractNpmPackageWithRetry(destPath, version) {
|
|
234
|
-
let lastErr;
|
|
235
|
-
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
236
|
-
try {
|
|
237
|
-
log(`npm extract attempt ${attempt}/${MAX_ATTEMPTS}: ${NPM_PACKAGE}@${version}`);
|
|
238
|
-
await extractNpmPackageWasm(destPath, version);
|
|
239
|
-
return;
|
|
240
|
-
} catch (err) {
|
|
241
|
-
lastErr = err;
|
|
242
|
-
log(`attempt ${attempt} failed: ${err.message}`);
|
|
243
|
-
obsEvent('bootstrap', 'npm.extract.attempt_failed', { package: NPM_PACKAGE, attempt, max: MAX_ATTEMPTS, err: String(err.message || err) });
|
|
244
|
-
if (err && (err.code === 'ENOENT' || /ENOENT/.test(String(err.message || '')))) {
|
|
245
|
-
log(`npx binary unresolvable (ENOENT); skipping retries, falling back`);
|
|
246
|
-
throw err;
|
|
247
|
-
}
|
|
248
|
-
if (err && (err.code === 'EINVAL' || /EINVAL/.test(String(err.message || '')))) {
|
|
249
|
-
log(`spawn EINVAL on npx shim; skipping retries, falling back`);
|
|
250
|
-
throw err;
|
|
251
|
-
}
|
|
252
|
-
if (attempt < MAX_ATTEMPTS) {
|
|
253
|
-
const wait = BACKOFF_MS[attempt - 1] || 120000;
|
|
254
|
-
log(`backing off ${wait}ms`);
|
|
255
|
-
await new Promise(r => setTimeout(r, wait));
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
throw lastErr;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
127
|
async function bootstrap(opts) {
|
|
263
128
|
opts = opts || {};
|
|
264
129
|
const wrapperDir = opts.wrapperDir || __dirname;
|
|
@@ -282,7 +147,7 @@ async function bootstrap(opts) {
|
|
|
282
147
|
|
|
283
148
|
if (fs.existsSync(wasmFinalPath) && fs.existsSync(wasmOkSentinel)) {
|
|
284
149
|
if (wasmExpectedSha) {
|
|
285
|
-
const actualSha = sha256OfFileSync(wasmFinalPath);
|
|
150
|
+
const actualSha = require('../gm-plugkit/gm-process').sha256OfFileSync(wasmFinalPath);
|
|
286
151
|
if (actualSha === wasmExpectedSha) {
|
|
287
152
|
obsEvent('bootstrap', 'decision.hit', { reason: 'sha-match', version, path: wasmFinalPath });
|
|
288
153
|
copyWasmToGmTools(wasmFinalPath, wrapperDir, version);
|
|
@@ -332,7 +197,7 @@ async function bootstrap(opts) {
|
|
|
332
197
|
if (fs.existsSync(wasmPartialPath)) {
|
|
333
198
|
try {
|
|
334
199
|
const st = fs.statSync(wasmPartialPath);
|
|
335
|
-
if (Date.now() - st.mtimeMs >
|
|
200
|
+
if (Date.now() - st.mtimeMs > 30 * 60 * 1000) {
|
|
336
201
|
fs.unlinkSync(wasmPartialPath);
|
|
337
202
|
log(`cleared stale partial: ${wasmPartialPath}`);
|
|
338
203
|
}
|
|
@@ -351,7 +216,7 @@ async function bootstrap(opts) {
|
|
|
351
216
|
}
|
|
352
217
|
|
|
353
218
|
if (wasmExpectedSha) {
|
|
354
|
-
const got = await sha256OfFile(wasmPartialPath);
|
|
219
|
+
const got = await require('../gm-plugkit/gm-process').sha256OfFile(wasmPartialPath);
|
|
355
220
|
if (got !== wasmExpectedSha) {
|
|
356
221
|
try { fs.unlinkSync(wasmPartialPath); } catch (_) {}
|
|
357
222
|
writeBootstrapError({
|
|
@@ -396,11 +261,7 @@ function getWasmPath(opts) {
|
|
|
396
261
|
try { const r = cacheRoot(); ensureDir(r); return r; }
|
|
397
262
|
catch (_) { const r = fallbackCacheRoot(); ensureDir(r); return r; }
|
|
398
263
|
})();
|
|
399
|
-
|
|
400
|
-
const wasmPath = path.join(verDir, 'plugkit.wasm');
|
|
401
|
-
const okSentinel = path.join(verDir, '.wasm-ok');
|
|
402
|
-
if (fs.existsSync(wasmPath) && fs.existsSync(okSentinel)) return wasmPath;
|
|
403
|
-
return null;
|
|
264
|
+
return resolveCacheWasmPath(root, version, 'plugkit.wasm');
|
|
404
265
|
}
|
|
405
266
|
|
|
406
267
|
function killStaleDaemonIfVersionChanged(wrapperDir) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
ffa850254a06a6a16e190b88a64dd960c369aae0a2ba2e985edc7a72ff11e779 plugkit-slim.wasm
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.1049
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d0008048fd103533948b5d91e48890ba3ff9b7d4544d00260bfa443d99077d8d plugkit.wasm
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
const { pidAlive, sha256OfFileSync } = require('./gm-process');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const LOCK_STALE_MS = 30 * 60 * 1000;
|
|
11
|
+
const ATTEMPT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
function obsEvent(subsystem, event, fields) {
|
|
14
|
+
if (process.env.GM_LOG_DISABLE) return;
|
|
15
|
+
try {
|
|
16
|
+
const root = process.env.GM_LOG_DIR || path.join(os.homedir(), '.claude', 'gm-log');
|
|
17
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
18
|
+
const dir = path.join(root, day);
|
|
19
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
20
|
+
const line = JSON.stringify({
|
|
21
|
+
ts: new Date().toISOString(),
|
|
22
|
+
sub: subsystem,
|
|
23
|
+
event,
|
|
24
|
+
pid: process.pid,
|
|
25
|
+
sess: process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
|
|
26
|
+
...fields,
|
|
27
|
+
});
|
|
28
|
+
fs.appendFileSync(path.join(dir, `${subsystem}.jsonl`), line + '\n');
|
|
29
|
+
} catch (_) {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function cacheRoot() {
|
|
33
|
+
const home = os.homedir();
|
|
34
|
+
if (process.env.PLUGKIT_CACHE_DIR) return process.env.PLUGKIT_CACHE_DIR;
|
|
35
|
+
if (os.platform() === 'win32') {
|
|
36
|
+
const base = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
37
|
+
return path.join(base, 'plugkit', 'bin');
|
|
38
|
+
}
|
|
39
|
+
if (os.platform() === 'darwin') return path.join(home, 'Library', 'Caches', 'plugkit', 'bin');
|
|
40
|
+
const xdg = process.env.XDG_CACHE_HOME || path.join(home, '.cache');
|
|
41
|
+
return path.join(xdg, 'plugkit', 'bin');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fallbackCacheRoot() {
|
|
45
|
+
return path.join(os.tmpdir(), 'plugkit-cache', 'bin');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function gmToolsDir() {
|
|
49
|
+
const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
50
|
+
const primary = path.join(home, '.gm-tools');
|
|
51
|
+
const fallback = path.join(home, '.claude', 'gm-tools');
|
|
52
|
+
if (fs.existsSync(primary)) return primary;
|
|
53
|
+
if (fs.existsSync(fallback)) return fallback;
|
|
54
|
+
return primary;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ensureDir(dir) {
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function acquireLock(lockPath) {
|
|
62
|
+
const start = Date.now();
|
|
63
|
+
for (;;) {
|
|
64
|
+
try {
|
|
65
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
66
|
+
fs.writeSync(fd, String(process.pid));
|
|
67
|
+
fs.closeSync(fd);
|
|
68
|
+
return true;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err.code !== 'EEXIST') throw err;
|
|
71
|
+
let stale = false;
|
|
72
|
+
try {
|
|
73
|
+
const st = fs.statSync(lockPath);
|
|
74
|
+
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) stale = true;
|
|
75
|
+
const owner = parseInt(fs.readFileSync(lockPath, 'utf8').trim(), 10);
|
|
76
|
+
if (Number.isFinite(owner) && owner !== process.pid && !pidAlive(owner)) stale = true;
|
|
77
|
+
} catch (_) { stale = true; }
|
|
78
|
+
if (stale) {
|
|
79
|
+
try { fs.unlinkSync(lockPath); } catch (_) {}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (Date.now() - start > ATTEMPT_TIMEOUT_MS) throw new Error(`lock wait timeout: ${lockPath}`);
|
|
83
|
+
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000); }
|
|
84
|
+
catch (e) { obsEvent('bootstrap', 'acquire-lock.atomics-wait-failed', { error: e.message, lockPath }); }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function releaseLock(lockPath) {
|
|
90
|
+
try { fs.unlinkSync(lockPath); } catch (_) {}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isLockStale(lockPath) {
|
|
94
|
+
try {
|
|
95
|
+
const st = fs.statSync(lockPath);
|
|
96
|
+
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) return true;
|
|
97
|
+
const owner = parseInt(fs.readFileSync(lockPath, 'utf8').trim(), 10);
|
|
98
|
+
if (Number.isFinite(owner) && !pidAlive(owner)) return true;
|
|
99
|
+
} catch (_) { return true; }
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function pruneOldVersions(root, keepVersion) {
|
|
104
|
+
try {
|
|
105
|
+
const entries = fs.readdirSync(root);
|
|
106
|
+
for (const e of entries) {
|
|
107
|
+
if (!e.startsWith('v')) continue;
|
|
108
|
+
if (e === `v${keepVersion}`) continue;
|
|
109
|
+
const dir = path.join(root, e);
|
|
110
|
+
const lock = path.join(dir, '.lock');
|
|
111
|
+
if (fs.existsSync(lock) && !isLockStale(lock)) continue;
|
|
112
|
+
if (fs.existsSync(lock)) { try { fs.unlinkSync(lock); } catch (_) {} }
|
|
113
|
+
try {
|
|
114
|
+
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 1, retryDelay: 50 });
|
|
115
|
+
} catch (_) {}
|
|
116
|
+
}
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function healIfShaMatches(binPath, expectedSha, sentinelPath, partialPath, kind) {
|
|
121
|
+
if (!fs.existsSync(binPath)) return false;
|
|
122
|
+
if (partialPath) { try { if (fs.existsSync(partialPath)) fs.unlinkSync(partialPath); } catch (_) {} }
|
|
123
|
+
if (!expectedSha) return false;
|
|
124
|
+
let got;
|
|
125
|
+
try { got = sha256OfFileSync(binPath); }
|
|
126
|
+
catch (_) { return false; }
|
|
127
|
+
if (got !== expectedSha) {
|
|
128
|
+
try { fs.unlinkSync(binPath); } catch (_) {}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
try { fs.writeFileSync(sentinelPath, new Date().toISOString()); } catch (_) { return false; }
|
|
132
|
+
obsEvent('bootstrap', 'cache.heal', { path: binPath, kind });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function daemonVersionSentinel() {
|
|
137
|
+
const root = (() => {
|
|
138
|
+
try { const r = cacheRoot(); ensureDir(r); return r; }
|
|
139
|
+
catch (_) { const r = fallbackCacheRoot(); ensureDir(r); return r; }
|
|
140
|
+
})();
|
|
141
|
+
return path.join(root, '.daemon-version');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function readDaemonVersion() {
|
|
145
|
+
try { return fs.readFileSync(daemonVersionSentinel(), 'utf8').trim(); }
|
|
146
|
+
catch (_) { return null; }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function writeDaemonVersion(v) {
|
|
150
|
+
try { fs.writeFileSync(daemonVersionSentinel(), String(v)); } catch (_) {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function pidCommandLineForKillGuard(pid) {
|
|
154
|
+
try {
|
|
155
|
+
if (process.platform === 'win32') {
|
|
156
|
+
const r = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `(Get-CimInstance Win32_Process -Filter "ProcessId=${Number(pid)}").CommandLine`], { encoding: 'utf8', windowsHide: true, timeout: 5000 });
|
|
157
|
+
return String((r && r.stdout) || '');
|
|
158
|
+
}
|
|
159
|
+
const r = spawnSync('ps', ['-p', String(pid), '-o', 'args='], { encoding: 'utf8', timeout: 5000 });
|
|
160
|
+
return String((r && r.stdout) || '');
|
|
161
|
+
} catch (_) { return ''; }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function pidIsPlugkitProcess(pid) {
|
|
165
|
+
return /agentplug-runner(\.exe)?/i.test(pidCommandLineForKillGuard(pid));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeKillAttribution(targetSpoolDir, info) {
|
|
169
|
+
try {
|
|
170
|
+
fs.mkdirSync(targetSpoolDir, { recursive: true });
|
|
171
|
+
fs.writeFileSync(path.join(targetSpoolDir, '.kill-attribution.json'), JSON.stringify({ killer_pid: process.pid, killer_cwd: process.cwd(), killer_script: __filename, ts: Date.now(), ...info }, null, 2));
|
|
172
|
+
} catch (_) {}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function killPid(pid) {
|
|
176
|
+
if (!Number.isFinite(pid) || pid === process.pid || !pidAlive(pid)) return false;
|
|
177
|
+
try { process.kill(pid, 'SIGTERM'); }
|
|
178
|
+
catch (_) { try { process.kill(pid); } catch (_) {} }
|
|
179
|
+
if (os.platform() === 'win32' && pidAlive(pid)) {
|
|
180
|
+
try { spawnSync('taskkill', ['/F', '/PID', String(pid)], { stdio: 'ignore', windowsHide: true, timeout: 3000, killSignal: 'SIGKILL' }); } catch (_) {}
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function killSpoolWatcherInCwd(reason) {
|
|
186
|
+
try {
|
|
187
|
+
const pidPath = path.join(process.cwd(), '.gm', 'exec-spool', '.watcher.pid');
|
|
188
|
+
if (!fs.existsSync(pidPath)) return null;
|
|
189
|
+
const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
|
|
190
|
+
if (pidAlive(pid) && !pidIsPlugkitProcess(pid)) {
|
|
191
|
+
obsEvent('bootstrap', 'watcher.kill-skipped-pid-reused', { pid, reason });
|
|
192
|
+
try { fs.unlinkSync(pidPath); } catch (_) {}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
writeKillAttribution(path.join(process.cwd(), '.gm', 'exec-spool'), { reason, target_pid: pid, via: 'killSpoolWatcherInCwd' });
|
|
196
|
+
if (killPid(pid)) {
|
|
197
|
+
obsEvent('bootstrap', 'watcher.killed', { pid, reason });
|
|
198
|
+
try { fs.unlinkSync(pidPath); } catch (_) {}
|
|
199
|
+
return pid;
|
|
200
|
+
}
|
|
201
|
+
try { fs.unlinkSync(pidPath); } catch (_) {}
|
|
202
|
+
} catch (_) {}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function proactiveKillForNewInstall(installedVersion) {
|
|
207
|
+
try {
|
|
208
|
+
const reason = `install:v${installedVersion}`;
|
|
209
|
+
killSpoolWatcherInCwd(reason);
|
|
210
|
+
writeDaemonVersion(installedVersion);
|
|
211
|
+
} catch (_) {}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function resolveWindowsExe(cmd) {
|
|
215
|
+
if (process.platform !== 'win32') return cmd;
|
|
216
|
+
try {
|
|
217
|
+
const r = spawnSync('where', [cmd], {
|
|
218
|
+
encoding: 'utf-8',
|
|
219
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
220
|
+
windowsHide: true,
|
|
221
|
+
timeout: 800,
|
|
222
|
+
});
|
|
223
|
+
if (r.status !== 0) return cmd;
|
|
224
|
+
const lines = (r.stdout || '').split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
225
|
+
const exe = lines.find(l => /\.exe$/i.test(l));
|
|
226
|
+
const shim = lines.find(l => /\.(cmd|bat)$/i.test(l));
|
|
227
|
+
return exe || shim || cmd;
|
|
228
|
+
} catch {
|
|
229
|
+
return cmd;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function resolveNpmCliJs(shimPath) {
|
|
234
|
+
const candidates = [];
|
|
235
|
+
try {
|
|
236
|
+
const execDir = path.dirname(process.execPath);
|
|
237
|
+
candidates.push(path.join(execDir, 'node_modules', 'npm', 'bin', 'npm-cli.js'));
|
|
238
|
+
} catch (_) {}
|
|
239
|
+
try {
|
|
240
|
+
const shimDir = path.dirname(shimPath);
|
|
241
|
+
candidates.push(path.join(shimDir, 'node_modules', 'npm', 'bin', 'npm-cli.js'));
|
|
242
|
+
} catch (_) {}
|
|
243
|
+
candidates.push(
|
|
244
|
+
path.join('C:', 'Program Files', 'nodejs', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
245
|
+
path.join(process.env.APPDATA || '', 'npm', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
246
|
+
path.join(process.env.APPDATA || '', 'nvm', process.version.replace(/^v/, ''), 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
247
|
+
);
|
|
248
|
+
return candidates.find(p => { try { return fs.existsSync(p); } catch (_) { return false; } }) || null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function ensureNextStepWiring(cwd) {
|
|
252
|
+
const changes = [];
|
|
253
|
+
const gmDir = path.join(cwd, '.gm');
|
|
254
|
+
try { fs.mkdirSync(gmDir, { recursive: true }); }
|
|
255
|
+
catch (e) { obsEvent('bootstrap', 'next-step.wiring.target-failed', { target: gmDir, error: e.message }); }
|
|
256
|
+
|
|
257
|
+
const nextStepPath = path.join(gmDir, 'next-step.md');
|
|
258
|
+
const nextStepBody = '# Next step\n\nDispatch the `instruction` verb to plugkit by writing `.gm/exec-spool/in/instruction/<N>.txt` (any unique N) with body `{}` (or `{"prompt":"<user request>"}` on the first dispatch of the turn). Read the response from `.gm/exec-spool/out/<N>.json` and follow the imperative in the `instruction` field.\n\nThis file is auto-rewritten by plugkit on every instruction dispatch.\n';
|
|
259
|
+
try {
|
|
260
|
+
if (!fs.existsSync(nextStepPath)) {
|
|
261
|
+
fs.writeFileSync(nextStepPath, nextStepBody);
|
|
262
|
+
changes.push('seeded .gm/next-step.md');
|
|
263
|
+
}
|
|
264
|
+
} catch (e) { obsEvent('bootstrap', 'next-step.wiring.target-failed', { target: nextStepPath, error: e.message }); }
|
|
265
|
+
|
|
266
|
+
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
267
|
+
try {
|
|
268
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
269
|
+
fs.writeFileSync(claudeMdPath, '@AGENTS.md\n');
|
|
270
|
+
changes.push('created CLAUDE.md');
|
|
271
|
+
} else {
|
|
272
|
+
const cur = fs.readFileSync(claudeMdPath, 'utf8');
|
|
273
|
+
const hasLine = cur.split(/\r?\n/).some(l => l.trim() === '@AGENTS.md');
|
|
274
|
+
if (!hasLine) {
|
|
275
|
+
fs.writeFileSync(claudeMdPath, '@AGENTS.md\n' + cur);
|
|
276
|
+
changes.push('prepended @AGENTS.md to CLAUDE.md');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch (e) { obsEvent('bootstrap', 'next-step.wiring.target-failed', { target: claudeMdPath, error: e.message }); }
|
|
280
|
+
|
|
281
|
+
const agentsMdPath = path.join(cwd, 'AGENTS.md');
|
|
282
|
+
try {
|
|
283
|
+
if (fs.existsSync(agentsMdPath)) {
|
|
284
|
+
const cur = fs.readFileSync(agentsMdPath, 'utf8');
|
|
285
|
+
const hasLine = cur.split(/\r?\n/).some(l => l.trim() === '@.gm/next-step.md');
|
|
286
|
+
if (!hasLine) {
|
|
287
|
+
const sep = cur.endsWith('\n') ? '' : '\n';
|
|
288
|
+
fs.writeFileSync(agentsMdPath, cur + sep + '\n@.gm/next-step.md\n');
|
|
289
|
+
changes.push('appended @.gm/next-step.md to AGENTS.md');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (e) { obsEvent('bootstrap', 'next-step.wiring.target-failed', { target: agentsMdPath, error: e.message }); }
|
|
293
|
+
|
|
294
|
+
return changes;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = {
|
|
298
|
+
obsEvent,
|
|
299
|
+
cacheRoot,
|
|
300
|
+
fallbackCacheRoot,
|
|
301
|
+
gmToolsDir,
|
|
302
|
+
ensureDir,
|
|
303
|
+
acquireLock,
|
|
304
|
+
releaseLock,
|
|
305
|
+
isLockStale,
|
|
306
|
+
pruneOldVersions,
|
|
307
|
+
healIfShaMatches,
|
|
308
|
+
daemonVersionSentinel,
|
|
309
|
+
readDaemonVersion,
|
|
310
|
+
writeDaemonVersion,
|
|
311
|
+
pidCommandLineForKillGuard,
|
|
312
|
+
pidIsPlugkitProcess,
|
|
313
|
+
writeKillAttribution,
|
|
314
|
+
killPid,
|
|
315
|
+
killSpoolWatcherInCwd,
|
|
316
|
+
proactiveKillForNewInstall,
|
|
317
|
+
ensureNextStepWiring,
|
|
318
|
+
resolveWindowsExe,
|
|
319
|
+
resolveNpmCliJs,
|
|
320
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
const { sha256OfFileSync } = require('./gm-process');
|
|
8
|
+
const {
|
|
9
|
+
obsEvent,
|
|
10
|
+
gmToolsDir,
|
|
11
|
+
resolveWindowsExe,
|
|
12
|
+
resolveNpmCliJs,
|
|
13
|
+
} = require('./bootstrap-shared');
|
|
14
|
+
|
|
15
|
+
const NPM_PACKAGE = 'plugkit-wasm';
|
|
16
|
+
const ATTEMPT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
17
|
+
const MAX_ATTEMPTS = 3;
|
|
18
|
+
const BACKOFF_MS = [5000, 15000];
|
|
19
|
+
|
|
20
|
+
function makeLogger(prefix) {
|
|
21
|
+
return function log(msg) {
|
|
22
|
+
try { process.stderr.write(`[${prefix}] ${msg}\n`); } catch (_) {}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveProjectRoot(start) {
|
|
27
|
+
const resolved = path.resolve(start);
|
|
28
|
+
try {
|
|
29
|
+
const r = spawnSync('git', ['rev-parse', '--git-common-dir'], { cwd: resolved, encoding: 'utf-8', windowsHide: true, timeout: 1500 });
|
|
30
|
+
if (r.status === 0 && r.stdout && r.stdout.trim()) {
|
|
31
|
+
let commonDir = r.stdout.trim();
|
|
32
|
+
if (!path.isAbsolute(commonDir)) commonDir = path.resolve(resolved, commonDir);
|
|
33
|
+
if (/(^|[\\/])\.git$/.test(commonDir)) return path.dirname(commonDir);
|
|
34
|
+
}
|
|
35
|
+
} catch (_) {}
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeBootstrapError(spec) {
|
|
40
|
+
try {
|
|
41
|
+
const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
42
|
+
const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
|
|
43
|
+
fs.mkdirSync(spoolDir, { recursive: true });
|
|
44
|
+
fs.writeFileSync(path.join(spoolDir, '.bootstrap-error.json'), JSON.stringify({ ts: new Date().toISOString(), ...spec }, null, 2));
|
|
45
|
+
} catch (_) {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function clearBootstrapError() {
|
|
49
|
+
try {
|
|
50
|
+
const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
51
|
+
fs.unlinkSync(path.join(projectDir, '.gm', 'exec-spool', '.bootstrap-error.json'));
|
|
52
|
+
} catch (_) {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readVersionFile(wrapperDir) {
|
|
56
|
+
const p = path.join(wrapperDir, 'plugkit.version');
|
|
57
|
+
if (!fs.existsSync(p)) throw new Error(`plugkit.version not found at ${p}`);
|
|
58
|
+
return fs.readFileSync(p, 'utf8').trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readShaManifest(wrapperDir, manifestName) {
|
|
62
|
+
const p = path.join(wrapperDir, manifestName || 'plugkit.sha256');
|
|
63
|
+
if (!fs.existsSync(p)) return null;
|
|
64
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(raw);
|
|
67
|
+
if (parsed && typeof parsed === 'object') {
|
|
68
|
+
const out = {};
|
|
69
|
+
for (const [name, sha] of Object.entries(parsed)) {
|
|
70
|
+
if (typeof sha === 'string') out[name] = sha.toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
} catch (_) {}
|
|
75
|
+
const out = {};
|
|
76
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
77
|
+
const m = line.match(/^([0-9a-f]{64})\s+(\S+)\s*$/i);
|
|
78
|
+
if (m) out[m[2]] = m[1].toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function copyWasmToGmTools(wasmPath, version, opts) {
|
|
84
|
+
opts = opts || {};
|
|
85
|
+
const dst = gmToolsDir();
|
|
86
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
87
|
+
const target = path.join(dst, 'plugkit.wasm');
|
|
88
|
+
|
|
89
|
+
let wasmFresh = false;
|
|
90
|
+
if (fs.existsSync(target)) {
|
|
91
|
+
try {
|
|
92
|
+
const cur = sha256OfFileSync(target);
|
|
93
|
+
const src = sha256OfFileSync(wasmPath);
|
|
94
|
+
if (cur === src) wasmFresh = true;
|
|
95
|
+
} catch (_) {}
|
|
96
|
+
}
|
|
97
|
+
if (!wasmFresh) {
|
|
98
|
+
const tmp = `${target}.partial-${process.pid}`;
|
|
99
|
+
fs.copyFileSync(wasmPath, tmp);
|
|
100
|
+
try { fs.renameSync(tmp, target); }
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err.code === 'EEXIST' || err.code === 'EPERM') {
|
|
103
|
+
try { fs.unlinkSync(target); } catch (_) {}
|
|
104
|
+
fs.renameSync(tmp, target);
|
|
105
|
+
} else {
|
|
106
|
+
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
|
|
112
|
+
|
|
113
|
+
if (opts.wrapperDir) {
|
|
114
|
+
try {
|
|
115
|
+
const srcSha = path.join(opts.wrapperDir, 'plugkit.sha256');
|
|
116
|
+
if (fs.existsSync(srcSha)) fs.copyFileSync(srcSha, path.join(dst, 'plugkit.sha256'));
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveCacheWasmPath(root, version, wasmName) {
|
|
122
|
+
const verDir = path.join(root, `v${version}`);
|
|
123
|
+
const wasmPath = path.join(verDir, wasmName || 'plugkit.wasm');
|
|
124
|
+
const okSentinel = path.join(verDir, '.wasm-ok');
|
|
125
|
+
if (fs.existsSync(wasmPath) && fs.existsSync(okSentinel)) return wasmPath;
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resolveInstalledWasmPath() {
|
|
130
|
+
const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
131
|
+
const primary = path.join(home, '.gm-tools', 'plugkit.wasm');
|
|
132
|
+
const fallback = path.join(home, '.claude', 'gm-tools', 'plugkit.wasm');
|
|
133
|
+
if (fs.existsSync(primary)) return primary;
|
|
134
|
+
if (fs.existsSync(fallback)) return fallback;
|
|
135
|
+
return primary;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function extractNpmPackageWasm(destPath, version, opts) {
|
|
139
|
+
opts = opts || {};
|
|
140
|
+
const log = opts.log || makeLogger('plugkit-bootstrap');
|
|
141
|
+
const { ensureDir } = require('./bootstrap-shared');
|
|
142
|
+
const tempDir = path.join(path.dirname(destPath), '.npm-extract-' + Date.now());
|
|
143
|
+
try {
|
|
144
|
+
ensureDir(tempDir);
|
|
145
|
+
const startMs = Date.now();
|
|
146
|
+
log(`extracting npm package ${NPM_PACKAGE}@${version} to ${tempDir}`);
|
|
147
|
+
obsEvent('bootstrap', 'npm.extract.start', { package: NPM_PACKAGE, version });
|
|
148
|
+
|
|
149
|
+
fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify({ name: 'plugkit-extract', version: '0.0.0', private: true }));
|
|
150
|
+
|
|
151
|
+
const cmd = resolveWindowsExe('npm');
|
|
152
|
+
const installArgs = ['install', '--no-audit', '--no-fund', '--no-save', NPM_PACKAGE + '@' + version];
|
|
153
|
+
const isCmdShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(cmd);
|
|
154
|
+
const npmCliJs = isCmdShim ? resolveNpmCliJs(cmd) : null;
|
|
155
|
+
|
|
156
|
+
const spawnCmd = npmCliJs ? process.execPath : (isCmdShim ? `"${cmd}"` : cmd);
|
|
157
|
+
const rawArgs = npmCliJs ? [npmCliJs, ...installArgs] : installArgs;
|
|
158
|
+
const spawnArgs = (isCmdShim && !npmCliJs) ? rawArgs.map(a => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a) : rawArgs;
|
|
159
|
+
|
|
160
|
+
const result = spawnSync(spawnCmd, spawnArgs, {
|
|
161
|
+
cwd: tempDir,
|
|
162
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
163
|
+
timeout: ATTEMPT_TIMEOUT_MS,
|
|
164
|
+
encoding: 'utf8',
|
|
165
|
+
windowsHide: true,
|
|
166
|
+
...((isCmdShim && !npmCliJs) ? { shell: true } : {}),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (result.error) throw result.error;
|
|
170
|
+
if (result.status !== 0) {
|
|
171
|
+
const detail = (result.stderr || result.stdout || '').trim().split(/\r?\n/).slice(-5).join(' | ');
|
|
172
|
+
const sig = result.signal ? ` signal=${result.signal}` : '';
|
|
173
|
+
throw new Error(`npm install failed status=${result.status}${sig}: ${detail || 'no stderr/stdout captured'}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const nodeModulesPath = path.join(tempDir, 'node_modules', NPM_PACKAGE, 'plugkit.wasm');
|
|
177
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
178
|
+
throw new Error(`plugkit.wasm not found in extracted npm package at ${nodeModulesPath}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fs.copyFileSync(nodeModulesPath, destPath);
|
|
182
|
+
log(`extracted ${nodeModulesPath} -> ${destPath}`);
|
|
183
|
+
obsEvent('bootstrap', 'npm.extract.end', { dur_ms: Date.now() - startMs, ok: true });
|
|
184
|
+
} finally {
|
|
185
|
+
try { fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 1, retryDelay: 50 }); } catch (_) {}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function extractNpmPackageWithRetry(destPath, version, opts) {
|
|
190
|
+
opts = opts || {};
|
|
191
|
+
const log = opts.log || makeLogger('plugkit-bootstrap');
|
|
192
|
+
let lastErr;
|
|
193
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
194
|
+
try {
|
|
195
|
+
log(`npm extract attempt ${attempt}/${MAX_ATTEMPTS}: ${NPM_PACKAGE}@${version}`);
|
|
196
|
+
await extractNpmPackageWasm(destPath, version, opts);
|
|
197
|
+
return;
|
|
198
|
+
} catch (err) {
|
|
199
|
+
lastErr = err;
|
|
200
|
+
log(`attempt ${attempt} failed: ${err.message}`);
|
|
201
|
+
obsEvent('bootstrap', 'npm.extract.attempt_failed', { package: NPM_PACKAGE, attempt, max: MAX_ATTEMPTS, err: String(err.message || err) });
|
|
202
|
+
if (err && (err.code === 'ENOENT' || /ENOENT/.test(String(err.message || '')))) {
|
|
203
|
+
log(`npm binary unresolvable (ENOENT); skipping retries, falling back`);
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
if (err && (err.code === 'EINVAL' || /EINVAL/.test(String(err.message || '')))) {
|
|
207
|
+
log(`spawn EINVAL on npm shim; skipping retries, falling back`);
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
211
|
+
const wait = BACKOFF_MS[attempt - 1] || 120000;
|
|
212
|
+
log(`backing off ${wait}ms`);
|
|
213
|
+
await new Promise(r => setTimeout(r, wait));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
throw lastErr;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
NPM_PACKAGE,
|
|
222
|
+
ATTEMPT_TIMEOUT_MS,
|
|
223
|
+
MAX_ATTEMPTS,
|
|
224
|
+
BACKOFF_MS,
|
|
225
|
+
makeLogger,
|
|
226
|
+
resolveProjectRoot,
|
|
227
|
+
writeBootstrapError,
|
|
228
|
+
clearBootstrapError,
|
|
229
|
+
readVersionFile,
|
|
230
|
+
readShaManifest,
|
|
231
|
+
copyWasmToGmTools,
|
|
232
|
+
resolveCacheWasmPath,
|
|
233
|
+
resolveInstalledWasmPath,
|
|
234
|
+
extractNpmPackageWasm,
|
|
235
|
+
extractNpmPackageWithRetry,
|
|
236
|
+
};
|
package/gm-plugkit/bootstrap.js
CHANGED
|
@@ -6,8 +6,9 @@ const path = require('path');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
8
|
const { spawn, spawnSync } = require('child_process');
|
|
9
|
-
const {
|
|
9
|
+
const { sha256OfFile, sha256OfFileSync } = require('./gm-process');
|
|
10
10
|
const shared = require('./bootstrap-shared');
|
|
11
|
+
const core = require('./bootstrap-wasm-core');
|
|
11
12
|
const {
|
|
12
13
|
obsEvent,
|
|
13
14
|
cacheRoot,
|
|
@@ -16,49 +17,40 @@ const {
|
|
|
16
17
|
ensureDir,
|
|
17
18
|
acquireLock,
|
|
18
19
|
releaseLock,
|
|
19
|
-
isLockStale,
|
|
20
20
|
pruneOldVersions,
|
|
21
21
|
healIfShaMatches,
|
|
22
22
|
daemonVersionSentinel,
|
|
23
23
|
readDaemonVersion,
|
|
24
24
|
writeDaemonVersion,
|
|
25
|
-
pidCommandLineForKillGuard,
|
|
26
|
-
pidIsPlugkitProcess,
|
|
27
|
-
writeKillAttribution,
|
|
28
|
-
killPid,
|
|
29
25
|
killSpoolWatcherInCwd,
|
|
30
26
|
proactiveKillForNewInstall,
|
|
31
27
|
ensureNextStepWiring: ensureNextStepWiringShared,
|
|
32
|
-
resolveWindowsExe,
|
|
33
|
-
resolveNpmCliJs,
|
|
34
28
|
} = shared;
|
|
29
|
+
const {
|
|
30
|
+
resolveProjectRoot,
|
|
31
|
+
writeBootstrapError,
|
|
32
|
+
clearBootstrapError,
|
|
33
|
+
resolveInstalledWasmPath,
|
|
34
|
+
} = core;
|
|
35
35
|
|
|
36
|
-
const NPM_PACKAGE = 'plugkit-wasm';
|
|
37
|
-
const ATTEMPT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
38
|
-
const MAX_ATTEMPTS = 3;
|
|
39
|
-
const BACKOFF_MS = [5000, 15000];
|
|
40
36
|
const LOCK_STALE_MS = 30 * 60 * 1000;
|
|
41
37
|
|
|
42
38
|
const wrapperDir = __dirname;
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
try { process.stderr.write(`[gm-plugkit] ${msg}\n`); } catch (_) {}
|
|
46
|
-
}
|
|
40
|
+
const log = core.makeLogger('gm-plugkit');
|
|
47
41
|
|
|
48
|
-
function
|
|
42
|
+
function copyWasmToGmTools(wasmPath, version) {
|
|
43
|
+
core.copyWasmToGmTools(wasmPath, version);
|
|
49
44
|
try {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
const ownPkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
|
|
46
|
+
if (ownPkg && ownPkg.version) {
|
|
47
|
+
fs.writeFileSync(path.join(gmToolsDir(), 'gm-plugkit.version'), ownPkg.version);
|
|
48
|
+
}
|
|
54
49
|
} catch (_) {}
|
|
55
50
|
}
|
|
56
51
|
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
60
|
-
fs.unlinkSync(path.join(projectDir, '.gm', 'exec-spool', '.bootstrap-error.json'));
|
|
61
|
-
} catch (_) {}
|
|
52
|
+
function extractNpmPackageWithRetry(destPath, version) {
|
|
53
|
+
return core.extractNpmPackageWithRetry(destPath, version, { log });
|
|
62
54
|
}
|
|
63
55
|
|
|
64
56
|
function sha256Hex(buf) {
|
|
@@ -179,45 +171,12 @@ function hasNativeEmbedRunner() {
|
|
|
179
171
|
return names.some(n => { try { return fs.existsSync(path.join(dir, n)); } catch (_) { return false; } });
|
|
180
172
|
}
|
|
181
173
|
|
|
182
|
-
function resolveProjectRoot(start) {
|
|
183
|
-
const resolved = path.resolve(start);
|
|
184
|
-
try {
|
|
185
|
-
const r = spawnSync('git', ['rev-parse', '--git-common-dir'], { cwd: resolved, encoding: 'utf-8', windowsHide: true, timeout: 1500 });
|
|
186
|
-
if (r.status === 0 && r.stdout && r.stdout.trim()) {
|
|
187
|
-
let commonDir = r.stdout.trim();
|
|
188
|
-
if (!path.isAbsolute(commonDir)) commonDir = path.resolve(resolved, commonDir);
|
|
189
|
-
if (/(^|[\\/])\.git$/.test(commonDir)) return path.dirname(commonDir);
|
|
190
|
-
}
|
|
191
|
-
} catch (_) {}
|
|
192
|
-
return resolved;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
174
|
function readVersionFile() {
|
|
196
|
-
|
|
197
|
-
if (!fs.existsSync(p)) throw new Error(`plugkit.version not found at ${p}`);
|
|
198
|
-
return fs.readFileSync(p, 'utf8').trim();
|
|
175
|
+
return core.readVersionFile(wrapperDir);
|
|
199
176
|
}
|
|
200
177
|
|
|
201
178
|
function readShaManifest() {
|
|
202
|
-
|
|
203
|
-
if (!fs.existsSync(p)) return null;
|
|
204
|
-
const raw = fs.readFileSync(p, 'utf8');
|
|
205
|
-
try {
|
|
206
|
-
const parsed = JSON.parse(raw);
|
|
207
|
-
if (parsed && typeof parsed === 'object') {
|
|
208
|
-
const out = {};
|
|
209
|
-
for (const [name, sha] of Object.entries(parsed)) {
|
|
210
|
-
if (typeof sha === 'string') out[name] = sha.toLowerCase();
|
|
211
|
-
}
|
|
212
|
-
return out;
|
|
213
|
-
}
|
|
214
|
-
} catch (_) {}
|
|
215
|
-
const out = {};
|
|
216
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
217
|
-
const m = line.match(/^([0-9a-f]{64})\s+(\S+)\s*$/i);
|
|
218
|
-
if (m) out[m[2]] = m[1].toLowerCase();
|
|
219
|
-
}
|
|
220
|
-
return out;
|
|
179
|
+
return core.readShaManifest(wrapperDir);
|
|
221
180
|
}
|
|
222
181
|
|
|
223
182
|
async function fetchRemoteSha(version, artifactName) {
|
|
@@ -231,54 +190,6 @@ async function fetchRemoteSha(version, artifactName) {
|
|
|
231
190
|
}
|
|
232
191
|
}
|
|
233
192
|
|
|
234
|
-
async function extractNpmPackageWasm(destPath, version) {
|
|
235
|
-
const tempDir = path.join(path.dirname(destPath), '.npm-extract-' + Date.now());
|
|
236
|
-
try {
|
|
237
|
-
ensureDir(tempDir);
|
|
238
|
-
const startMs = Date.now();
|
|
239
|
-
log(`extracting npm package ${NPM_PACKAGE}@${version} to ${tempDir}`);
|
|
240
|
-
obsEvent('bootstrap', 'npm.extract.start', { package: NPM_PACKAGE, version });
|
|
241
|
-
|
|
242
|
-
fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify({ name: 'plugkit-extract', version: '0.0.0', private: true }));
|
|
243
|
-
|
|
244
|
-
const cmd = resolveWindowsExe('npm');
|
|
245
|
-
const installArgs = ['install', '--no-audit', '--no-fund', '--no-save', NPM_PACKAGE + '@' + version];
|
|
246
|
-
const isCmdShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(cmd);
|
|
247
|
-
const npmCliJs = isCmdShim ? resolveNpmCliJs(cmd) : null;
|
|
248
|
-
|
|
249
|
-
const spawnCmd = npmCliJs ? process.execPath : (isCmdShim ? `"${cmd}"` : cmd);
|
|
250
|
-
const rawArgs = npmCliJs ? [npmCliJs, ...installArgs] : installArgs;
|
|
251
|
-
const spawnArgs = (isCmdShim && !npmCliJs) ? rawArgs.map(a => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a) : rawArgs;
|
|
252
|
-
|
|
253
|
-
const result = spawnSync(spawnCmd, spawnArgs, {
|
|
254
|
-
cwd: tempDir,
|
|
255
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
256
|
-
timeout: ATTEMPT_TIMEOUT_MS,
|
|
257
|
-
encoding: 'utf8',
|
|
258
|
-
windowsHide: true,
|
|
259
|
-
...((isCmdShim && !npmCliJs) ? { shell: true } : {}),
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
if (result.error) throw result.error;
|
|
263
|
-
if (result.status !== 0) {
|
|
264
|
-
const detail = (result.stderr || result.stdout || '').trim().split(/\r?\n/).slice(-5).join(' | ');
|
|
265
|
-
const sig = result.signal ? ` signal=${result.signal}` : '';
|
|
266
|
-
throw new Error(`npm install failed status=${result.status}${sig}: ${detail || 'no stderr/stdout captured'}`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const nodeModulesPath = path.join(tempDir, 'node_modules', NPM_PACKAGE, 'plugkit.wasm');
|
|
270
|
-
if (!fs.existsSync(nodeModulesPath)) {
|
|
271
|
-
throw new Error(`plugkit.wasm not found in extracted npm package at ${nodeModulesPath}`);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
fs.copyFileSync(nodeModulesPath, destPath);
|
|
275
|
-
log(`extracted ${nodeModulesPath} -> ${destPath}`);
|
|
276
|
-
obsEvent('bootstrap', 'npm.extract.end', { dur_ms: Date.now() - startMs, ok: true });
|
|
277
|
-
} finally {
|
|
278
|
-
try { fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 1, retryDelay: 50 }); } catch (_) {}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
193
|
function httpGetBuffer(url, timeoutMs) {
|
|
283
194
|
const https = require('https');
|
|
284
195
|
const idleTimeoutMs = timeoutMs || 30000;
|
|
@@ -355,35 +266,6 @@ async function downloadFromGithubReleases(destPath, version, artifactName) {
|
|
|
355
266
|
log(`gh-releases wrote ${buf.length} bytes to ${destPath} (artifact=${name})`);
|
|
356
267
|
}
|
|
357
268
|
|
|
358
|
-
async function extractNpmPackageWithRetry(destPath, version) {
|
|
359
|
-
let lastErr;
|
|
360
|
-
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
361
|
-
try {
|
|
362
|
-
log(`npm extract attempt ${attempt}/${MAX_ATTEMPTS}: ${NPM_PACKAGE}@${version}`);
|
|
363
|
-
await extractNpmPackageWasm(destPath, version);
|
|
364
|
-
return;
|
|
365
|
-
} catch (err) {
|
|
366
|
-
lastErr = err;
|
|
367
|
-
log(`attempt ${attempt} failed: ${err.message}`);
|
|
368
|
-
obsEvent('bootstrap', 'npm.extract.attempt_failed', { package: NPM_PACKAGE, attempt, max: MAX_ATTEMPTS, err: String(err.message || err) });
|
|
369
|
-
if (err && (err.code === 'ENOENT' || /ENOENT/.test(String(err.message || '')))) {
|
|
370
|
-
log(`npm binary unresolvable (ENOENT); skipping retries, falling back`);
|
|
371
|
-
throw err;
|
|
372
|
-
}
|
|
373
|
-
if (err && (err.code === 'EINVAL' || /EINVAL/.test(String(err.message || '')))) {
|
|
374
|
-
log(`spawn EINVAL on npm shim; skipping retries, falling back`);
|
|
375
|
-
throw err;
|
|
376
|
-
}
|
|
377
|
-
if (attempt < MAX_ATTEMPTS) {
|
|
378
|
-
const wait = BACKOFF_MS[attempt - 1] || 120000;
|
|
379
|
-
log(`backing off ${wait}ms`);
|
|
380
|
-
await new Promise(r => setTimeout(r, wait));
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
throw lastErr;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
269
|
function killStaleDaemonIfVersionChanged() {
|
|
388
270
|
let currentVersion;
|
|
389
271
|
try { currentVersion = readVersionFile(); }
|
|
@@ -391,9 +273,9 @@ function killStaleDaemonIfVersionChanged() {
|
|
|
391
273
|
obsEvent('bootstrap', 'kill-stale-daemon.version-read-failed', { error: e.message });
|
|
392
274
|
return;
|
|
393
275
|
}
|
|
394
|
-
const cached =
|
|
395
|
-
if (cached) {
|
|
396
|
-
proactiveKillForNewInstall(currentVersion
|
|
276
|
+
const cached = resolveInstalledWasmPath();
|
|
277
|
+
if (cached && fs.existsSync(cached)) {
|
|
278
|
+
proactiveKillForNewInstall(currentVersion);
|
|
397
279
|
return;
|
|
398
280
|
}
|
|
399
281
|
const recorded = readDaemonVersion();
|
|
@@ -553,51 +435,8 @@ async function bootstrap(opts) {
|
|
|
553
435
|
}
|
|
554
436
|
}
|
|
555
437
|
|
|
556
|
-
function copyWasmToGmTools(wasmPath, version) {
|
|
557
|
-
const dst = gmToolsDir();
|
|
558
|
-
fs.mkdirSync(dst, { recursive: true });
|
|
559
|
-
const target = path.join(dst, 'plugkit.wasm');
|
|
560
|
-
|
|
561
|
-
let wasmFresh = false;
|
|
562
|
-
if (fs.existsSync(target)) {
|
|
563
|
-
try {
|
|
564
|
-
const cur = sha256OfFileSync(target);
|
|
565
|
-
const src = sha256OfFileSync(wasmPath);
|
|
566
|
-
if (cur === src) wasmFresh = true;
|
|
567
|
-
} catch (_) {}
|
|
568
|
-
}
|
|
569
|
-
if (!wasmFresh) {
|
|
570
|
-
const tmp = `${target}.partial-${process.pid}`;
|
|
571
|
-
fs.copyFileSync(wasmPath, tmp);
|
|
572
|
-
try { fs.renameSync(tmp, target); }
|
|
573
|
-
catch (err) {
|
|
574
|
-
if (err.code === 'EEXIST' || err.code === 'EPERM') {
|
|
575
|
-
try { fs.unlinkSync(target); } catch (_) {}
|
|
576
|
-
fs.renameSync(tmp, target);
|
|
577
|
-
} else {
|
|
578
|
-
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
579
|
-
throw err;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
|
|
584
|
-
|
|
585
|
-
try {
|
|
586
|
-
const ownPkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
|
|
587
|
-
if (ownPkg && ownPkg.version) {
|
|
588
|
-
fs.writeFileSync(path.join(dst, 'gm-plugkit.version'), ownPkg.version);
|
|
589
|
-
}
|
|
590
|
-
} catch (_) {}
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
|
|
594
438
|
function getWasmPath() {
|
|
595
|
-
|
|
596
|
-
const primary = path.join(home, '.gm-tools', 'plugkit.wasm');
|
|
597
|
-
const fallback = path.join(home, '.claude', 'gm-tools', 'plugkit.wasm');
|
|
598
|
-
if (fs.existsSync(primary)) return primary;
|
|
599
|
-
if (fs.existsSync(fallback)) return fallback;
|
|
600
|
-
return primary;
|
|
439
|
+
return resolveInstalledWasmPath();
|
|
601
440
|
}
|
|
602
441
|
|
|
603
442
|
function isReady() {
|
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2210",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform wasm, verifies SHA256, and launches agentplug-runner (the native wasm host) as the spool watcher daemon.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
ffa850254a06a6a16e190b88a64dd960c369aae0a2ba2e985edc7a72ff11e779 plugkit-slim.wasm
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.1049
|
package/gm.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2210",
|
|
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.
|
|
20
|
+
"plugkitVersion": "0.1.1049"
|
|
21
21
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2210",
|
|
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,6 +38,8 @@
|
|
|
38
38
|
"bin/plugkit.wasm.sha256",
|
|
39
39
|
"bin/plugkit-slim.wasm.sha256",
|
|
40
40
|
"gm-plugkit/bootstrap.js",
|
|
41
|
+
"gm-plugkit/bootstrap-wasm-core.js",
|
|
42
|
+
"gm-plugkit/bootstrap-shared.js",
|
|
41
43
|
"gm-plugkit/cli.js",
|
|
42
44
|
"gm-plugkit/index.js",
|
|
43
45
|
"gm-plugkit/package.json",
|