gm-plugkit 2.0.2469 → 2.0.2471
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/SKILL.md +2 -2
- package/bootstrap.js +111 -4
- package/package.json +1 -1
- package/plugkit.version +1 -1
package/SKILL.md
CHANGED
|
@@ -60,7 +60,7 @@ bun x gm-plugkit@latest spool
|
|
|
60
60
|
|
|
61
61
|
(`npx -y gm-plugkit@latest spool` if no `bun`.) Fire-and-forget: spawns the detached daemon and returns immediately (already-alive watcher also returns at once, unchanged) -- it does NOT wait for the watcher to check serving. No `&`, no `sleep`, no re-`cat`; write your first verb to `in/` right after it returns. A first-read "file does not exist" on that verb is normal (the just-spawned watcher hasn't noticed the file yet) -- re-Read next message, same as any dead-watcher-adjacent recheck. If you need to actively check serving before dispatching (rare), read `.gm/exec-spool/.status.json` yourself and check `ts` freshness.
|
|
62
62
|
|
|
63
|
-
**The boot line resolves to agentplug-runner, the sole spool loader; `bun x gm-plugkit@latest spool` is the thin launcher that re-execs into it.** `gm-plugkit/cli.js::tryDelegateToRunner` execs `~/.gm-tools/agentplug-runner` -- a native wasmtime binary that loads gm.wasm alongside shared `bert`/`libsql`/`treesitter` plugins (so gm runs `plugkit-slim.wasm`, browser via direct CDP, task via a native registry); `.status.json` `runtime` reads `agentplug` when it serves.
|
|
63
|
+
**The boot line resolves to agentplug-runner, the sole spool loader; `bun x gm-plugkit@latest spool` is the thin launcher that re-execs into it.** `gm-plugkit/cli.js::tryDelegateToRunner` execs `~/.gm-tools/agentplug-runner` -- a native wasmtime binary that loads gm.wasm alongside shared `bert`/`libsql`/`treesitter` plugins (so gm runs `plugkit-slim.wasm`, browser via direct CDP, task via a native registry); `.status.json` `runtime` reads `agentplug` when it serves. A missing runner binary is downloaded and sha256-verified automatically on the next boot attempt -- no separate install step. agentplug-runner auto-updates both its served `plugkit.wasm` and its own executable fully autonomously (600s poll each): a newer runner build is staged to `.new` in the background, then swapped in via a self-triggered `takeover` handoff on the next idle tick -- no agent action, no restart ever required.
|
|
64
64
|
|
|
65
65
|
The `Resolving dependencies` / `Saved lockfile` chatter before the JSON payload is `bunx` re-resolving the `@latest` tag against the registry, not gm-plugkit hanging -- the daemon already spawns detached+unref'd and the CLI itself exits the instant that happens; the visible delay is entirely bunx's own network round-trip, unavoidable on `@latest` (a pinned exact version, once bunx-cached, skips it). `GM_PLUGKIT_SKIP_SELF_STALE_CHECK=1` skips the CLI's own redundant npm-registry version probe (already covered by `@latest`'s resolution) for a faster boot on repeat same-session invocations. In PowerShell 5.1, never `2>&1`-redirect this command into another cmdlet (e.g. `| Select-Object`) -- PowerShell wraps every stderr line from a native exe in a `NativeCommandError` record and reports failure even on exit 0, turning bun's routine stderr progress output into a misleading red error block; run it bare or capture stdout only.
|
|
66
66
|
|
|
@@ -74,7 +74,7 @@ Two more real, code-checked env toggles beyond `GM_PLUGKIT_SKIP_SELF_STALE_CHECK
|
|
|
74
74
|
|
|
75
75
|
**Reboot-loop escape (watcher dies ~30-90s after every boot).** If a fresh `bun x gm-plugkit@latest spool` boots but the watcher dies again shortly after (heartbeat `ts` goes stale >30s with no future `busy_until`, then a new pid appears, repeatedly), the on-disk index has not finished embedding and each boot re-triggers the same synchronous code-index embed that can block the heartbeat past the supervisor's 30s stale limit (`STATUS_STALE_MS`). Confirm by reading `.gm/exec-spool/.watcher.log` for repeated `codeinsight_rebuild` + `partial pass (wall budget) ... deferred_files=N` lines whose `deferred_files` never reaches 0. The embed is genuinely converging in that case: do NOT immediately re-boot on the first stale reading -- read `.watcher.log`, and as long as `deferred_files` is strictly decreasing across `codeinsight_index_partial` events the index is converging (each accepted verb advances it one wall-budget); give it repeated single verbs until a `code_index: done` / `deferred_files=0` line appears, then normal dispatch resumes. Rebooting mid-convergence resets this progress -- the loop is the reboot, not the embed.
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
A `dispatch_orphaned` response ("claimed by a daemon that died before answering... a self-update handoff") is expected during a runner takeover, never a real failure -- bare re-dispatch the exact same verb once `.status.json`'s `ts` is fresh again. `.status.json`'s `last_completed_runner_swap`/`loaded_plugin_versions` are diagnostic-only, worth diffing against a prior read to notice served behavior changed mid-session; neither changes what to dispatch next.
|
|
78
78
|
|
|
79
79
|
**Apparent tooling failure is never grounds to ask the user, never a blind restart.** "Spooler not working" / missing response / stale watcher / `gm_plugkit_stale` flagged in a response = your own mechanical self-recovery: honor a future `busy_until` (wait), else boot + re-dispatch. You have boot authority; asking the user to do what a verb can do is a deviation. Staleness of any kind (stale watcher version, stale served prose vs published source) is itself a deviation to resolve immediately, the same turn it's noticed -- `bun x gm-plugkit@latest spool` first, before any other work.
|
|
80
80
|
|
package/bootstrap.js
CHANGED
|
@@ -231,6 +231,24 @@ function httpGetBuffer(url, timeoutMs) {
|
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
function httpHeadOk(url, timeoutMs) {
|
|
235
|
+
const https = require('https');
|
|
236
|
+
return new Promise((resolve, reject) => {
|
|
237
|
+
const req = https.request(url, { method: 'HEAD', timeout: timeoutMs || 3000, headers: { 'user-agent': 'gm-plugkit-bootstrap' } }, (res) => {
|
|
238
|
+
res.resume();
|
|
239
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
240
|
+
httpHeadOk(res.headers.location, timeoutMs).then(resolve, reject);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (res.statusCode === 200) resolve();
|
|
244
|
+
else reject(new Error(`HTTP ${res.statusCode} ${url}`));
|
|
245
|
+
});
|
|
246
|
+
req.on('timeout', () => { try { req.destroy(new Error(`idle-timeout ${timeoutMs || 3000}ms ${url}`)); } catch (_) {} });
|
|
247
|
+
req.on('error', reject);
|
|
248
|
+
req.end();
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
234
252
|
async function downloadFromGithubReleases(destPath, version, artifactName) {
|
|
235
253
|
const name = artifactName || 'plugkit.wasm';
|
|
236
254
|
const base = `https://github.com/AnEntrypoint/plugkit-bin/releases/download/v${version}`;
|
|
@@ -546,12 +564,59 @@ function discoverBundledSkillsAndSourcesLocal() {
|
|
|
546
564
|
return found;
|
|
547
565
|
}
|
|
548
566
|
|
|
567
|
+
// Walks a (decompressed) POSIX tar stream's fixed 512-byte headers, yielding
|
|
568
|
+
// each entry's name. `git archive --remote` was tried first and does NOT
|
|
569
|
+
// work against GitHub (its git server returns HTTP 422 for upload-archive,
|
|
570
|
+
// a documented GitHub limitation) -- codeload.github.com's tarball endpoint
|
|
571
|
+
// is the real plain-HTTPS, non-api.github.com path that actually serves
|
|
572
|
+
// repo content.
|
|
573
|
+
function* tarEntryNames(buf) {
|
|
574
|
+
let offset = 0;
|
|
575
|
+
while (offset + 512 <= buf.length) {
|
|
576
|
+
const header = buf.subarray(offset, offset + 512);
|
|
577
|
+
if (header.every(b => b === 0)) break;
|
|
578
|
+
const name = header.subarray(0, 100).toString('utf-8').replace(/\0.*$/s, '');
|
|
579
|
+
const sizeOctal = header.subarray(124, 136).toString('utf-8').replace(/\0.*$/s, '').trim();
|
|
580
|
+
const size = parseInt(sizeOctal, 8) || 0;
|
|
581
|
+
if (name) yield name;
|
|
582
|
+
offset += 512 + Math.ceil(size / 512) * 512;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function discoverRemoteSkillNamesViaCodeload(timeoutMs) {
|
|
587
|
+
const url = `https://codeload.github.com/${SKILL_MD_REMOTE_REPO}/tar.gz/refs/heads/${SKILL_MD_REMOTE_BRANCH}`;
|
|
588
|
+
const gz = await httpGetBuffer(url, timeoutMs || 15000);
|
|
589
|
+
const tar = require('zlib').gunzipSync(gz);
|
|
590
|
+
const names = new Set();
|
|
591
|
+
for (const name of tarEntryNames(tar)) {
|
|
592
|
+
const m = /^[^/]+\/skills\/([^/]+)\/$/.exec(name);
|
|
593
|
+
if (m) names.add(m[1]);
|
|
594
|
+
}
|
|
595
|
+
if (names.size === 0) throw new Error('codeload tarball listing produced zero skill directories');
|
|
596
|
+
return Array.from(names);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// The GitHub Contents API this normally uses already fails soft (caller
|
|
600
|
+
// falls back to bundled local skills), but a fully API-scope-restricted
|
|
601
|
+
// environment (a corporate proxy or org policy blocking api.github.com
|
|
602
|
+
// specifically) loses fresh remote skill discovery entirely even though
|
|
603
|
+
// the same repo is reachable over codeload.github.com's plain-HTTPS
|
|
604
|
+
// tarball endpoint -- a different host, no API token or REST surface.
|
|
549
605
|
async function discoverRemoteSkillNames(timeoutMs) {
|
|
550
606
|
const url = `https://api.github.com/repos/${SKILL_MD_REMOTE_REPO}/contents/skills?ref=${SKILL_MD_REMOTE_BRANCH}`;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
607
|
+
try {
|
|
608
|
+
const buf = await httpGetBuffer(url, timeoutMs || 5000);
|
|
609
|
+
const entries = JSON.parse(buf.toString('utf-8'));
|
|
610
|
+
if (!Array.isArray(entries)) throw new Error('unexpected github contents API response shape');
|
|
611
|
+
return entries.filter(e => e && e.type === 'dir' && e.name).map(e => e.name);
|
|
612
|
+
} catch (apiErr) {
|
|
613
|
+
try {
|
|
614
|
+
return await discoverRemoteSkillNamesViaCodeload(timeoutMs);
|
|
615
|
+
} catch (fallbackErr) {
|
|
616
|
+
obsEvent('bootstrap', 'discover-remote-skill-names.codeload-fallback-failed', { error: fallbackErr.message });
|
|
617
|
+
throw apiErr;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
555
620
|
}
|
|
556
621
|
|
|
557
622
|
async function fetchRemoteSkillMd(skillName, timeoutMs) {
|
|
@@ -659,6 +724,42 @@ function installedVersionAtTools() {
|
|
|
659
724
|
} catch (_) { return null; }
|
|
660
725
|
}
|
|
661
726
|
|
|
727
|
+
function compareDottedSemverAscending(a, b) {
|
|
728
|
+
const pa = a.replace(/^v/, '').split('.').map(Number);
|
|
729
|
+
const pb = b.replace(/^v/, '').split('.').map(Number);
|
|
730
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
731
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
732
|
+
if (d !== 0) return d;
|
|
733
|
+
}
|
|
734
|
+
return 0;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Fallback when the Releases-list API is unreachable: git ls-remote can
|
|
738
|
+
// see every tag over plain git protocol without touching api.github.com,
|
|
739
|
+
// but cannot see release ASSETS -- so a tag it finds is verified against
|
|
740
|
+
// a HEAD probe on the raw release-download URL for plugkit.wasm before
|
|
741
|
+
// being trusted, matching the API path's own hasPlugkitWasm check.
|
|
742
|
+
async function resolveLatestRemoteVersionViaGit(timeoutMs) {
|
|
743
|
+
const { execFileSync } = require('child_process');
|
|
744
|
+
const out = execFileSync('git', ['ls-remote', '--tags', '--refs', 'https://github.com/AnEntrypoint/plugkit-bin.git'], {
|
|
745
|
+
encoding: 'utf8',
|
|
746
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
747
|
+
});
|
|
748
|
+
const tags = out.split('\n')
|
|
749
|
+
.map(line => line.match(/refs\/tags\/v(\d+\.\d+\.\d+(?:-[A-Za-z0-9.]+)?)$/))
|
|
750
|
+
.filter(Boolean)
|
|
751
|
+
.map(m => m[1]);
|
|
752
|
+
tags.sort(compareDottedSemverAscending);
|
|
753
|
+
for (let i = tags.length - 1; i >= 0; i--) {
|
|
754
|
+
const version = tags[i];
|
|
755
|
+
try {
|
|
756
|
+
await httpHeadOk(`https://github.com/AnEntrypoint/plugkit-bin/releases/download/v${version}/plugkit.wasm`, timeoutMs || 3000);
|
|
757
|
+
return version;
|
|
758
|
+
} catch (_) { /* asset missing for this tag, try the next-newest */ }
|
|
759
|
+
}
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
|
|
662
763
|
async function resolveLatestRemoteVersion(timeoutMs) {
|
|
663
764
|
try {
|
|
664
765
|
const buf = await httpGetBuffer('https://api.github.com/repos/AnEntrypoint/plugkit-bin/releases?per_page=50', timeoutMs || 3000);
|
|
@@ -674,6 +775,12 @@ async function resolveLatestRemoteVersion(timeoutMs) {
|
|
|
674
775
|
}
|
|
675
776
|
} catch (e) {
|
|
676
777
|
obsEvent('bootstrap', 'resolve-latest-remote-version.failed', { error: e.message });
|
|
778
|
+
try {
|
|
779
|
+
const viaGit = await resolveLatestRemoteVersionViaGit(timeoutMs);
|
|
780
|
+
if (viaGit) return viaGit;
|
|
781
|
+
} catch (gitErr) {
|
|
782
|
+
obsEvent('bootstrap', 'resolve-latest-remote-version.git-fallback-failed', { error: gitErr.message });
|
|
783
|
+
}
|
|
677
784
|
}
|
|
678
785
|
return null;
|
|
679
786
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2471",
|
|
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": {
|
package/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.1183
|