gm-skill 2.0.2013 → 2.0.2015
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/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +48 -7
- package/gm-plugkit/wrapper/cdp-eval.js +123 -0
- package/gm.json +1 -1
- package/package.json +1 -1
- package/skills/gm/SKILL.md +1 -1
package/AGENTS.md
CHANGED
|
@@ -106,7 +106,7 @@ A task that reduces to read/investigate/report, or a change confined to files th
|
|
|
106
106
|
|
|
107
107
|
**Done is plugkit's pronouncement, never the agent's claim**: COMPLETE only when `transition to=COMPLETE` returns COMPLETE phase, on-disk state reflects it. COMPLETE gate (gates.rs) = sole arbiter -- refuses on PRD-open, mutables-unresolved, dirty worktree, missing residual-scan marker. Agent drives into gate-allowing state, dispatches, reads response; every alternative is narration.
|
|
108
108
|
|
|
109
|
-
**Every residual is triaged this turn, never named-and-deferred**: every `git status --porcelain` entry at residual-scan/COMPLETE-attempt triaged now -- (a) commit (real work), (b) managed-gitignore-block (transient runtime emission, e.g. `.gm/witness/`, `.gm/exec-spool/.*-stale.json`), (c) revert (junk). "Pre-existing" = the (a)/(c) outcome, never a stop excuse. `blockedBy: external`
|
|
109
|
+
**Every residual is triaged this turn, never named-and-deferred**: every `git status --porcelain` entry at residual-scan/COMPLETE-attempt triaged now -- (a) commit (real work), (b) managed-gitignore-block (transient runtime emission, e.g. `.gm/witness/`, `.gm/exec-spool/.*-stale.json`), (c) revert (junk). "Pre-existing" = the (a)/(c) outcome, never a stop excuse. Everything is fixable: when triage points outside the local tree, the row's job is the concrete reach action that builds past it (open the PR, script the credential path, replace/drive-directly the broken tool), never a `blockedBy: external` resting state -- `blockedBy` only transiently carries that reach path forward while it is built, and local-tree files are always agent-authoritative to fix now. Disciplines + new memorize-fire `mem-*.json` committed. Managed block (`# >>> plugkit managed` markers) carries only zero-future-read-value runtime artifacts.
|
|
110
110
|
|
|
111
111
|
**"Every possible" is the load-bearing test, applied to every noun the request names** -- PRD density at PLAN, second-pass corner-case transform: the recall store (`recall: PLAN-phase PRD density rule`).
|
|
112
112
|
|
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.2015",
|
|
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": {
|
|
@@ -2564,6 +2564,9 @@ function makeHostFunctions(instanceRef) {
|
|
|
2564
2564
|
const __topNM = modeOpts.match(/topN=(\d+)/);
|
|
2565
2565
|
const sampleIntervalUs = __intervalM && parseInt(__intervalM[1], 10) > 0 ? parseInt(__intervalM[1], 10) : 100;
|
|
2566
2566
|
const profileTopNBrowser = __topNM && parseInt(__topNM[1], 10) > 0 ? parseInt(__topNM[1], 10) : 20;
|
|
2567
|
+
const rawUserScript = (screenshotPath || domSelector)
|
|
2568
|
+
? null
|
|
2569
|
+
: (modeMatch ? modeMatch[3] : evalBody);
|
|
2567
2570
|
// Real, pre-existing bug fixed here: this block previously called `page.evaluateOnNewDocument`,
|
|
2568
2571
|
// which is a PUPPETEER method name -- playwriter's `page` object is a real Playwright Page,
|
|
2569
2572
|
// whose equivalent is `page.addInitScript`. evaluateOnNewDocument does not exist on a
|
|
@@ -2772,13 +2775,51 @@ function makeHostFunctions(instanceRef) {
|
|
|
2772
2775
|
// A temp .js file has no such length limit -- this sidesteps the Bun bug class entirely rather
|
|
2773
2776
|
// than working around it script-by-script.
|
|
2774
2777
|
const scriptFile = path.join(os.tmpdir(), `gm-browser-eval-${process.pid}-${execProfileSeq++}.js`);
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2778
|
+
// The playwriter relay's attach+eval hop crashes with a UV_HANDLE_CLOSING native
|
|
2779
|
+
// assertion on Windows. When the session already has a live Chrome CDP endpoint
|
|
2780
|
+
// (chromium launched with --remote-debugging-port, recorded in browser-ports.json),
|
|
2781
|
+
// drive that endpoint directly via the proven wrapper/cdp-eval.js helper instead --
|
|
2782
|
+
// it connects over the DevTools websocket and runs the raw user script through
|
|
2783
|
+
// Runtime.evaluate, never spawning the crashing relay. This is gated strictly on a
|
|
2784
|
+
// live CDP port AND a self-contained raw user script (screenshot=/dom= bodies build
|
|
2785
|
+
// page.screenshot/page.evaluate calls that need the Playwright page object cdp-eval.js
|
|
2786
|
+
// does not provide, so rawUserScript is null for those and the playwriter path runs).
|
|
2787
|
+
let usedCdp = false;
|
|
2788
|
+
if (rawUserScript != null) {
|
|
2789
|
+
let cdpPort = null;
|
|
2790
|
+
let cdpWs = null;
|
|
2791
|
+
try {
|
|
2792
|
+
const ent = readJsonFile(browserPortsFile(cwd), {})[sessionId];
|
|
2793
|
+
if (ent && Number.isFinite(ent.port)) { cdpPort = ent.port; cdpWs = ent.wsEndpoint || null; }
|
|
2794
|
+
} catch (_) {}
|
|
2795
|
+
const cdpLive = cdpPort != null && !!fetchJsonSync(`http://127.0.0.1:${cdpPort}/json/version`, 1000);
|
|
2796
|
+
if (cdpLive) {
|
|
2797
|
+
const cdpEvalScript = path.join(os.tmpdir(), `gm-cdp-eval-${process.pid}-${execProfileSeq++}.js`);
|
|
2798
|
+
try {
|
|
2799
|
+
fs.writeFileSync(cdpEvalScript, rawUserScript, 'utf-8');
|
|
2800
|
+
const cfg = JSON.stringify({ port: cdpPort, wsEndpoint: cdpWs, startUrl, scriptFile: cdpEvalScript, resultFile, timeoutMs });
|
|
2801
|
+
r = spawnSync(process.execPath, [path.join(__dirname, 'wrapper', 'cdp-eval.js'), cfg], {
|
|
2802
|
+
encoding: 'utf-8',
|
|
2803
|
+
timeout: outerTimeoutMs,
|
|
2804
|
+
windowsHide: true,
|
|
2805
|
+
});
|
|
2806
|
+
usedCdp = true;
|
|
2807
|
+
} finally {
|
|
2808
|
+
try { fs.unlinkSync(cdpEvalScript); } catch (_) {}
|
|
2809
|
+
clearInflight(sessionId);
|
|
2810
|
+
stampBrowserLastUse(cwd, sessionId);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
if (!usedCdp) {
|
|
2815
|
+
try {
|
|
2816
|
+
fs.writeFileSync(scriptFile, evalBody, 'utf-8');
|
|
2817
|
+
r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', String(timeoutMs), '-f', scriptFile], outerTimeoutMs, cwd, sessionId);
|
|
2818
|
+
} finally {
|
|
2819
|
+
try { fs.unlinkSync(scriptFile); } catch (_) {}
|
|
2820
|
+
clearInflight(sessionId);
|
|
2821
|
+
stampBrowserLastUse(cwd, sessionId);
|
|
2822
|
+
}
|
|
2782
2823
|
}
|
|
2783
2824
|
const ok = r.status === 0;
|
|
2784
2825
|
if (!ok && r.status === null) {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
|
|
4
|
+
function httpJson(url, timeoutMs) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const req = http.get(url, { timeout: timeoutMs }, (res) => {
|
|
7
|
+
let body = '';
|
|
8
|
+
res.on('data', (c) => { body += c; });
|
|
9
|
+
res.on('end', () => { try { resolve(JSON.parse(body)); } catch (_) { resolve(null); } });
|
|
10
|
+
});
|
|
11
|
+
req.on('error', () => resolve(null));
|
|
12
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function pickPageTarget(port, startUrl, timeoutMs) {
|
|
17
|
+
const deadline = Date.now() + timeoutMs;
|
|
18
|
+
while (Date.now() < deadline) {
|
|
19
|
+
const list = await httpJson(`http://127.0.0.1:${port}/json/list`, 2000);
|
|
20
|
+
if (Array.isArray(list)) {
|
|
21
|
+
const page = list.find((t) => t.type === 'page' && t.webSocketDebuggerUrl);
|
|
22
|
+
if (page) return page;
|
|
23
|
+
}
|
|
24
|
+
if (startUrl) {
|
|
25
|
+
const created = await httpJson(`http://127.0.0.1:${port}/json/new?${encodeURIComponent(startUrl)}`, 3000);
|
|
26
|
+
if (created && created.webSocketDebuggerUrl) return created;
|
|
27
|
+
}
|
|
28
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cdpSession(wsUrl, timeoutMs) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const ws = new WebSocket(wsUrl);
|
|
36
|
+
let nextId = 1;
|
|
37
|
+
const pending = new Map();
|
|
38
|
+
const timer = setTimeout(() => { try { ws.close(); } catch (_) {} reject(new Error('cdp timeout')); }, timeoutMs);
|
|
39
|
+
ws.addEventListener('open', () => {
|
|
40
|
+
resolve({
|
|
41
|
+
send(method, params) {
|
|
42
|
+
const id = nextId++;
|
|
43
|
+
return new Promise((res, rej) => {
|
|
44
|
+
pending.set(id, { res, rej });
|
|
45
|
+
ws.send(JSON.stringify({ id, method, params: params || {} }));
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
close() { clearTimeout(timer); try { ws.close(); } catch (_) {} },
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
ws.addEventListener('message', (ev) => {
|
|
52
|
+
let msg;
|
|
53
|
+
try { msg = JSON.parse(ev.data); } catch (_) { return; }
|
|
54
|
+
if (msg.id && pending.has(msg.id)) {
|
|
55
|
+
const { res, rej } = pending.get(msg.id);
|
|
56
|
+
pending.delete(msg.id);
|
|
57
|
+
if (msg.error) rej(new Error(msg.error.message || 'cdp error'));
|
|
58
|
+
else res(msg.result);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
ws.addEventListener('error', () => { clearTimeout(timer); reject(new Error('cdp websocket error')); });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Direct CDP evaluation, replacing the playwriter relay attach+eval that crashes
|
|
66
|
+
// with UV_HANDLE_CLOSING on Windows. Everything up to obtaining Chrome's CDP
|
|
67
|
+
// endpoint is already done by the wrapper (it launches Chrome with
|
|
68
|
+
// --remote-debugging-port and polls /json/version); this drives that endpoint
|
|
69
|
+
// directly over the DevTools websocket, so the crashing relay process is never
|
|
70
|
+
// spawned. Reads {port, startUrl, scriptFile, resultFile, timeoutMs} from argv[2]
|
|
71
|
+
// as JSON, runs the script via Runtime.evaluate (awaitPromise, returnByValue),
|
|
72
|
+
// and writes the returned value to resultFile -- the same result channel the
|
|
73
|
+
// playwriter path used.
|
|
74
|
+
async function main() {
|
|
75
|
+
const cfg = JSON.parse(process.argv[2]);
|
|
76
|
+
const { port, startUrl, scriptFile, resultFile, timeoutMs } = cfg;
|
|
77
|
+
const script = fs.readFileSync(scriptFile, 'utf-8');
|
|
78
|
+
const target = await pickPageTarget(port, startUrl, Math.min(timeoutMs, 30000));
|
|
79
|
+
if (!target) {
|
|
80
|
+
fs.writeFileSync(resultFile, JSON.stringify({ __cdpError: 'no page target on CDP endpoint' }));
|
|
81
|
+
process.stderr.write('cdp-eval: no page target\n');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const sess = await cdpSession(target.webSocketDebuggerUrl, timeoutMs);
|
|
85
|
+
try {
|
|
86
|
+
await sess.send('Runtime.enable', {});
|
|
87
|
+
if (startUrl) {
|
|
88
|
+
await sess.send('Page.enable', {});
|
|
89
|
+
await sess.send('Page.navigate', { url: startUrl });
|
|
90
|
+
await new Promise((r) => setTimeout(r, 1200));
|
|
91
|
+
}
|
|
92
|
+
// The wrapper's eval body is a function body ending in `return __RET;`.
|
|
93
|
+
// Wrap it in an async IIFE so Runtime.evaluate resolves the returned value.
|
|
94
|
+
const wrapped = `(async () => { ${script} })()`;
|
|
95
|
+
const res = await sess.send('Runtime.evaluate', {
|
|
96
|
+
expression: wrapped,
|
|
97
|
+
awaitPromise: true,
|
|
98
|
+
returnByValue: true,
|
|
99
|
+
userGesture: true,
|
|
100
|
+
timeout: timeoutMs,
|
|
101
|
+
});
|
|
102
|
+
if (res.exceptionDetails) {
|
|
103
|
+
const msg = res.exceptionDetails.exception && res.exceptionDetails.exception.description
|
|
104
|
+
? res.exceptionDetails.exception.description
|
|
105
|
+
: (res.exceptionDetails.text || 'evaluate exception');
|
|
106
|
+
fs.writeFileSync(resultFile, JSON.stringify({ __cdpError: msg }));
|
|
107
|
+
process.stderr.write(`cdp-eval: exception ${msg}\n`);
|
|
108
|
+
sess.close();
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const value = res.result && ('value' in res.result) ? res.result.value : null;
|
|
112
|
+
fs.writeFileSync(resultFile, JSON.stringify(value === undefined ? null : value));
|
|
113
|
+
sess.close();
|
|
114
|
+
process.exit(0);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
fs.writeFileSync(resultFile, JSON.stringify({ __cdpError: String(e && e.message || e) }));
|
|
117
|
+
process.stderr.write(`cdp-eval: ${e && e.message || e}\n`);
|
|
118
|
+
try { sess.close(); } catch (_) {}
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main();
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2015",
|
|
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",
|
package/skills/gm/SKILL.md
CHANGED
|
@@ -94,7 +94,7 @@ If similar symptoms recur (stub-like responses, silent truncation, debug fields
|
|
|
94
94
|
|
|
95
95
|
**Windows-specific transient flakiness that is NOT a plugkit/wrapper bug, just retry it:** `bun x <pkg>@<version>` occasionally hangs indefinitely at "Resolving dependencies" with zero further output even with a pinned exact cached version (a known intermittent Bun/Windows dependency-resolution stall, reproducible with plain `bun x playwriter@<version> session list` outside any wrapper). A bare re-dispatch of the exact same body, or a fresh direct CLI retry, has resolved it every time observed. Do not chase this as a code bug -- 2-3 retries is the correct response, same bound as the raw-CLI fragility note below.
|
|
96
96
|
|
|
97
|
-
**
|
|
97
|
+
**The browser verb drives Chrome directly over CDP -- the playwriter relay is no longer on the eval path.** The `UV_HANDLE_CLOSING` native crash in playwriter's own relay process on Windows ("Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\\win\\async.c, line 76") was resolved by removing the dependency on that relay: the wrapper already launches Chrome with `--remote-debugging-port` and obtains a live DevTools `webSocketDebuggerUrl` (playwriter-free), and `gm-plugkit/wrapper/cdp-eval.js` now runs the script in-page via `Runtime.evaluate` over that websocket directly, so the crashing relay attach+eval is never spawned. This is the standing browser eval path. A flaky browser witness is therefore a real bug to root-cause in the CDP path or the Chrome launch, never a `blockedBy: external` tooling gap -- everything is fixable: if a browser dispatch fails, read the CDP endpoint (`http://127.0.0.1:<port>/json/version`), check the Chrome launch args, and fix the actual failure rather than parking it. There is no accepted "external tooling gap" resolution for the browser verb.
|
|
98
98
|
|
|
99
99
|
Spool input from PowerShell must be UTF-8 no-BOM (`-Encoding utf8` or `[System.IO.File]::WriteAllText`); UTF-16+BOM causes `spool.body-encoding-recoded`. First-turn body is `{"prompt":"<user request>"}` (derives orient_nouns + recall_hits); later turns may use `{}`. Batch independent dispatches: multiple `prd-add`, `prd-resolve`, `mutable-add`, `recall`+`codesearch`, or inspection `Read` calls in a single tool block. Avoid editing the same file twice in one block; collapse changes into a single Edit.
|
|
100
100
|
|